<!DOCTYPE HTML>
<html lang="zh-CN">


<head>
    <meta charset="utf-8">
    <meta name="keywords" content="fisher1, 湮灭星空,博客,Python,后端">
    <meta name="description" content="fisher
flask 路由的基本原理
add_url_route可以传入rule、view_function、endpoint，如果不传endpoint，会默认将view_function的名字作为endpoint。
add_url_r">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <meta name="renderer" content="webkit|ie-stand|ie-comp">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="format-detection" content="telephone=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <title>fisher1 | 湮灭星空</title>
    <link rel="icon" type="image/png" href="/favicon.png">

    <link rel="stylesheet" type="text/css" href="/libs/awesome/css/all.css">
    <link rel="stylesheet" type="text/css" href="/libs/materialize/materialize.min.css">
    <link rel="stylesheet" type="text/css" href="/libs/aos/aos.css">
    <link rel="stylesheet" type="text/css" href="/libs/animate/animate.min.css">
    <link rel="stylesheet" type="text/css" href="/libs/lightGallery/css/lightgallery.min.css">
    <link rel="stylesheet" type="text/css" href="/css/matery.css">
    <link rel="stylesheet" type="text/css" href="/css/my.css">

    <script src="/libs/jquery/jquery.min.js"></script>

<link rel="alternate" href="/atom.xml" title="湮灭星空" type="application/atom+xml">
</head>

<body>
    <header class="navbar-fixed">
    <nav id="headNav" class="bg-color nav-transparent">
        <div id="navContainer" class="nav-wrapper head-container">
            <div class="brand-logo">
                <a href="/" class="waves-effect waves-light">
                    
                    <img src="/medias/logo.png" class="logo-img" alt="LOGO">
                    
                    <span class="logo-span">湮灭星空</span>
                </a>
            </div>
            

<a href="#" data-target="mobile-nav" class="sidenav-trigger button-collapse"><i class="fas fa-bars"></i></a>
<ul class="right nav-menu">
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/" class="waves-effect waves-light">
      
      <i class="fas fa-home" style="zoom: 0.6;"></i>
      
      <span>首页</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/tags" class="waves-effect waves-light">
      
      <i class="fas fa-tags" style="zoom: 0.6;"></i>
      
      <span>标签</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/categories" class="waves-effect waves-light">
      
      <i class="fas fa-bookmark" style="zoom: 0.6;"></i>
      
      <span>分类</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/archives" class="waves-effect waves-light">
      
      <i class="fas fa-archive" style="zoom: 0.6;"></i>
      
      <span>归档</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/about" class="waves-effect waves-light">
      
      <i class="fas fa-user-circle" style="zoom: 0.6;"></i>
      
      <span>关于</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/contact" class="waves-effect waves-light">
      
      <i class="fas fa-comments" style="zoom: 0.6;"></i>
      
      <span>留言板</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/friends" class="waves-effect waves-light">
      
      <i class="fas fa-address-book" style="zoom: 0.6;"></i>
      
      <span>友链</span>
    </a>
    
  </li>
  
  <li>
    <a href="#searchModal" class="modal-trigger waves-effect waves-light">
      <i id="searchIcon" class="fas fa-search" title="搜索" style="zoom: 0.85;"></i>
    </a>
  </li>
</ul>

<div id="mobile-nav" class="side-nav sidenav">

    <div class="mobile-head bg-color">
        
        <img src="/medias/logo.png" class="logo-img circle responsive-img">
        
        <div class="logo-name">湮灭星空</div>
        <div class="logo-desc">
            
            天下寥寥，苍生涂涂，诸子百家，唯我纵横
            
        </div>
    </div>

    

    <ul class="menu-list mobile-menu-list">
        
        <li class="m-nav-item">
	  
		<a href="/" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-home"></i>
			
			首页
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/tags" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-tags"></i>
			
			标签
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/categories" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-bookmark"></i>
			
			分类
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/archives" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-archive"></i>
			
			归档
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/about" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-user-circle"></i>
			
			关于
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/contact" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-comments"></i>
			
			留言板
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/friends" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-address-book"></i>
			
			友链
		</a>
          
        </li>
        
        
    </ul>
</div>

        </div>

        
    </nav>

</header>

    



<div class="bg-cover pd-header post-cover" style="background-image: url('/medias/featureimages/4.jpg')">
    <div class="container" style="right: 0px;left: 0px;">
        <div class="row">
            <div class="col s12 m12 l12">
                <div class="brand">
                    <div class="description center-align post-title">
                        fisher1
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>




<main class="post-container content">

    
    <link rel="stylesheet" href="/libs/tocbot/tocbot.css">
<style>
    #articleContent h1::before,
    #articleContent h2::before,
    #articleContent h3::before,
    #articleContent h4::before,
    #articleContent h5::before,
    #articleContent h6::before {
        display: block;
        content: " ";
        height: 100px;
        margin-top: -100px;
        visibility: hidden;
    }

    #articleContent :focus {
        outline: none;
    }

    .toc-fixed {
        position: fixed;
        top: 64px;
    }

    .toc-widget {
        padding-left: 20px;
    }

    .toc-widget .toc-title {
        margin: 35px 0 15px 0;
        padding-left: 17px;
        font-size: 1.5rem;
        font-weight: bold;
        line-height: 1.5rem;
    }

    .toc-widget ol {
        padding: 0;
        list-style: none;
    }

    #toc-content ol {
        padding-left: 10px;
    }

    #toc-content ol li {
        padding-left: 10px;
    }

    #toc-content .toc-link:hover {
        color: #42b983;
        font-weight: 700;
        text-decoration: underline;
    }

    #toc-content .toc-link::before {
        background-color: transparent;
        max-height: 25px;
    }

    #toc-content .is-active-link {
        color: #42b983;
    }

    #toc-content .is-active-link::before {
        background-color: #42b983;
    }

    #floating-toc-btn {
        position: fixed;
        right: 15px;
        bottom: 76px;
        padding-top: 15px;
        margin-bottom: 0;
        z-index: 998;
    }

    #floating-toc-btn .btn-floating {
        width: 48px;
        height: 48px;
    }

    #floating-toc-btn .btn-floating i {
        line-height: 48px;
        font-size: 1.4rem;
    }
</style>
<div class="row">
    <div id="main-content" class="col s12 m12 l9">
        <!-- 文章内容详情 -->
<div id="artDetail">
    <div class="card">
        <div class="card-content article-info">
            <div class="row tag-cate">
                <div class="col s7">
                    
                    <div class="article-tag">
                        
                            <a href="/tags/python/">
                                <span class="chip bg-color">python</span>
                            </a>
                        
                            <a href="/tags/flask/">
                                <span class="chip bg-color">flask</span>
                            </a>
                        
                    </div>
                    
                </div>
                <div class="col s5 right-align">
                    
                    <div class="post-cate">
                        <i class="fas fa-bookmark fa-fw icon-category"></i>
                        
                            <a href="/categories/fisher/" class="post-category">
                                fisher
                            </a>
                        
                    </div>
                    
                </div>
            </div>

            <div class="post-info">
                
                <div class="post-date info-break-policy">
                    <i class="far fa-calendar-minus fa-fw"></i>发布日期:&nbsp;&nbsp;
                    2019-04-12
                </div>
                

                
                <div class="post-date info-break-policy">
                    <i class="far fa-calendar-check fa-fw"></i>更新日期:&nbsp;&nbsp;
                    2019-12-06
                </div>
                

                
                <div class="info-break-policy">
                    <i class="far fa-file-word fa-fw"></i>文章字数:&nbsp;&nbsp;
                    14.6k
                </div>
                

                
                <div class="info-break-policy">
                    <i class="far fa-clock fa-fw"></i>阅读时长:&nbsp;&nbsp;
                    55 分
                </div>
                
				
                
                    <div id="busuanzi_container_page_pv" class="info-break-policy">
                        <i class="far fa-eye fa-fw"></i>阅读次数:&nbsp;&nbsp;
                        <span id="busuanzi_value_page_pv"></span>
                    </div>
				
            </div>
            
        </div>
        <hr class="clearfix">
        <div class="card-content article-card-content">
            <div id="articleContent">
                <h1 id="fisher"><a class="header-anchor" href="#fisher"></a>fisher</h1>
<h3 id="flask-路由的基本原理"><a class="header-anchor" href="#flask-路由的基本原理"></a>flask 路由的基本原理</h3>
<p><code>add_url_route</code>可以传入<code>rule</code>、<code>view_function</code>、<code>endpoint</code>，如果不传<code>endpoint</code>，会默认将<code>view_function</code>的名字作为<code>endpoint</code>。</p>
<p><code>add_url_route</code>会将最终的<code>rule</code>存放在<code>url_map</code>里面，视图函数存放在<code>view_function</code>的字典里面。</p>
<blockquote>
<p><code>view_functions</code>为字典，键为<code>endpoint</code>，值为视图函数</p>
</blockquote>
<ul>
<li>
<p>首先<code>url_map</code>这个 <code>map</code>对象里面必须由我们的 <code>url</code> -&gt; <code>search endpoint </code>的指向；</p>
</li>
<li>
<p>同时<code>view_functions</code>里面必须记录<code>search endpoint</code>所指向的视图函数。</p>
</li>
</ul>
<p>这样当一个请求进入 <code>flask</code>之后才能根据 <code>url</code> 顺利的找到对应的视图函数，这就是 <code>flask</code>路由的基本原理。</p>
<h3 id="循环引用图解"><a class="header-anchor" href="#循环引用图解"></a>循环引用图解</h3>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132310.jpg" alt="image-20181123003843668"></p>
<h3 id="1-拆分视图函数到单独的模块中去"><a class="header-anchor" href="#1-拆分视图函数到单独的模块中去"></a>1. <strong>拆分视图函数</strong>到单独的模块中去</h3>
<p>将视图函数从主执行文件分离出来时，不能直接导入flask的核心对象，导致不能使用flask核心对象来注册视图函数的路由</p>
<h3 id="2-只有由视图函数或者http请求触发的request才能得到get返回的结果数据"><a class="header-anchor" href="#2-只有由视图函数或者http请求触发的request才能得到get返回的结果数据"></a>2. 只有由<strong>视图函数</strong>或者<code>http</code>请求触发的<code>request</code>才能得到<code>get</code>返回的结果数据</h3>
<ul>
<li><code>flask</code>默认<code>request</code>类型是<code>localproxy</code>(本地代理)</li>
</ul>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132315.jpg" alt="image-20181123084533135"></p>
<h3 id="3-验证层"><a class="header-anchor" href="#3-验证层"></a>3. <strong>验证层</strong></h3>
<p><strong>验证层</strong>：使用<code>wtforms</code>进行参数校验</p>
<h3 id="4-mvc模型"><a class="header-anchor" href="#4-mvc模型"></a>4. <strong>MVC</strong>模型</h3>
<ul>
<li><strong>MVC</strong>模型里绝对不是只有数据，如果只有数据的话，那只能算作数据表</li>
<li><strong>MVC</strong>里的<em>M</em>是一个业务模型，必须定义很多操作一个个数据字段的业务方法</li>
</ul>
<blockquote>
<p><strong>经典面试问题</strong>:<br>
业务逻辑应该写在<strong>MVC</strong>里的哪一层？<br>
业务逻辑最好应该是在<strong>M</strong>(<em>model</em>)层编写</p>
</blockquote>
<h3 id="5-orm的含义"><a class="header-anchor" href="#5-orm的含义"></a>5. <strong>ORM</strong>的含义</h3>
<p><strong>ORM</strong>：关系型数据库和实体间做映射，操作对象的属性和方法，跳过SQL语句</p>
<p>对象关系映射(英语:<code>Object Relational Mapping</code>，简称<code>ORM</code>，或<code>O/RM</code>，或<code>O/R mapping</code>)，用于实现面向对象编程语言里不同类型系统的数据之间的转换。其实是创建了一个可在编程语言里使用的<code>虚拟对象数据库</code>。<code>Object</code>是可以继承的，是可以使用接口的，而<code>Relation</code>没有这个概念。<br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132318.jpg" alt=""></p>
<h3 id="6-最基本的数据结构"><a class="header-anchor" href="#6-最基本的数据结构"></a>6. 最基本的数据结构</h3>
<ul>
<li><strong>栈</strong>：<strong>后进先出</strong></li>
<li><strong>队列</strong>：<strong>先进先出</strong></li>
</ul>
<h3 id="7-with语句"><a class="header-anchor" href="#7-with语句"></a>7. <strong><code>with</code>语句</strong></h3>
<p><code>with</code>语句：上下文管理器可以使用<code>with</code>语句</p>
<blockquote>
<p>实现了上下文协议的对象就可以使用<code>with</code>语句<br>
实现了上下文协议的对象通常称为<strong>上下文管理器</strong><br>
一个对象只要实现了<code>__enter__</code>和<code>__exit__</code>两个方法，就是实现了<strong>上下文协议</strong><br>
上下文表达式（with后面的表达式）必须返回一个上下文管理器</p>
</blockquote>
<p>示例1：</p>
<pre><code class="language-python">class A:
    def __enter__(self):
        a = 1

    def __exit__(self, exc_type, exc_val, exc_tb):
        b = 2

with A() as obj_A:  
    pass
</code></pre>
<ul>
<li><code>obj_A</code> 是 <code>None</code>；<code>A()</code>直接实例化 <code>A</code>，返回的是上下文管理器对象</li>
<li>as 语句后面的变量不是上下文管理器</li>
<li><code>__enter__</code> 方法所返回的值会赋给 as 语句后面的变量</li>
</ul>
<pre><code class="language-python">class A:
    def __enter__(self):
        a = 1
        return a

    def __exit__(self, exc_type, exc_val, exc_tb):
        b = 2

with A() as obj_A:  # obj_A ：1
    pass
</code></pre>
<p>示例2：<code>文件读写</code></p>
<pre><code class="language-python">try:
    f = open(r'D:\t.txt')
    print(f.read())
finally:
    f.close()
</code></pre>
<p>使用<strong>with语句</strong>改写</p>
<pre><code class="language-python">with open(r'D:\t.txt') as f:
    print(f.read())
</code></pre>
<p>示例3：<code>with语句处理异常</code></p>
<pre><code class="language-python">class MyResource:
    def __enter__(self):
        print('connect to resource')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_tb:
            print('process exception')
        else:
            print('no exception')
        print('close resource connection')
        # return True     # 返回 True 表示已经在 __exit__ 内部处理过异常，不需要在外部处理
        return False    # 返回 False 表示没有在 __exit__ 内部处理异常，需要在外部处理
        # 如果 __exit__ 没有返回，则默认返回 False

    def query(self):
        print('query data')


try:
    with MyResource() as resource:
        1/0
        resource.query()
except Exception as ex:
    print('with语句出现异常')
    pass
</code></pre>
<h3 id="8-操作数据库的流程"><a class="header-anchor" href="#8-操作数据库的流程"></a>8.操作数据库的流程</h3>
<ul>
<li>
<p>连接数据库</p>
<ul>
<li>
<p><code>flask-sqlalchemy</code>连接数据库</p>
<pre><code class="language-python">SQLALCHEMY_DATABASE_URI = 'mysql+cymysql://username:password@localhost:3306/fisher?charset=utf8'
</code></pre>
<p>参数解释：</p>
<p><code>cymysql</code>：<code>mysql</code>数据库的链接驱动</p>
<p><code>username:password@localhost:3306</code>：用户名：密码@服务器地址：端口</p>
<p><code>fisher</code>：数据库名称</p>
<p><code>charset=utf8</code>：指定数据库的编码方式为<code>utf8</code></p>
<blockquote>
<p>采坑：</p>
<p><strong><code>mysql</code>数据库必须指定编码方式，否则<code>commit</code>的时候会出错。</strong><br>
时间：2018年10月18日20:39:43<br>
就因此踩了坑，花费了三天的时间。刚开始没有指定数据库的编码方式，结果在用户之间发起鱼漂的时候，储存鱼漂到数据库的时候报如下错误：</p>
<pre><code>sqlalchemy.exc.InternalError: (cymysql.err.InternalError) (1366，...）
</code></pre>
<p>使用 <code>vscode</code>进行远程调试，主要调试了提交数据库的几个操作：</p>
<ul>
<li>
<p>用户注册的时候，需要储存用户数据到数据库，这类 <code>commit</code>没问题，储存的是 <code>user</code>表</p>
</li>
<li>
<p>赠送数据的时候，需要将礼物（书籍）数据添加到数据库，这类 <code>commit</code>没问题，储存的是 <code>gift</code>表</p>
</li>
<li>
<p>添加心愿书籍的时候，需要储存心愿，这类 <code>commit</code>没问题，储存的是 <code>wish</code>表</p>
</li>
<li>
<p>储存鱼漂的时候，<code>commit</code>就会报错，这类 <code>commit</code>储存的是 <code>drift</code>表</p>
</li>
</ul>
<p>查 <code>google</code>确实查到了是 <code>mysql</code>编码的问题，</p>
<ul>
<li>
<p>尝试1：修改 <code>mysql</code>编码模式为 <code>utf8</code>，结果：无效</p>
</li>
<li>
<p>尝试2：修改已创建的 <code>fisher</code>数据库的编码模式为 <code>utf8</code>，结果：无效</p>
</li>
<li>
<p>尝试3：修改<code>mysql</code>连接方式<code>SQLALCHEMY_DATABASE_URI = 'mysql+cymysql://username:password@localhost:3306/fisher?charset=utf8'</code>，结果：无效</p>
</li>
<li>
<p>尝试4：修改</p>
</li>
<li>
<p>尝试4：删除 <code>fisher</code>数据库，重新让 <code>sqlalchemy</code>建立数据表，结果：<strong>有效</strong></p>
</li>
</ul>
<p>原因嘛，猜测为<code>drift</code>表的编码模式出现了问题。</p>
<p>至于为什么其他表的编码模式没问题，只有 <code>drift</code>这个搞不清楚，以后在捉摸吧。</p>
</blockquote>
</li>
</ul>
</li>
<li>
<p><code>SQL</code>操作</p>
</li>
<li>
<p>释放资源</p>
</li>
</ul>
<p>使用</p>
<ul>
<li><code>try</code></li>
<li><code>except</code></li>
<li><code>finally</code></li>
</ul>
<p>无论出现什么异常，最终都会执行<code>final</code>语句，不会浪费资源，很优雅<br>
另一种方式就是使用<strong>with语句</strong></p>
<h3 id="9-进程和线程"><a class="header-anchor" href="#9-进程和线程"></a>9. 进程和线程</h3>
<h4 id="进程"><a class="header-anchor" href="#进程"></a><strong>进程</strong></h4>
<p>进程：是竞争计算机资源的基本单位</p>
<ul>
<li>每一个应用程序至少需要一个进程</li>
<li>进程是分配资源的</li>
<li>多进程管理的资源是不能相互访问的</li>
<li><strong>多进程</strong>资源共享需要使用<strong>进程通信技术</strong></li>
</ul>
<h4 id="线程"><a class="header-anchor" href="#线程"></a><strong>线程</strong></h4>
<p>线程：是进程的一部分，一个进程可以有一个或多个线程</p>
<ul>
<li>线程：利用 <code>cpu</code> 执行代码</li>
<li>线程属于进程</li>
<li>线程不拥有资源，但是可以访问进程的资源</li>
<li>多线程可以更加充分的利用 <code>cpu</code> 的性能优势</li>
<li>多个线程共享一个进程的资源，就会造成进程不安全</li>
</ul>
<h4 id="gil"><a class="header-anchor" href="#gil"></a><strong>GIL</strong></h4>
<p><strong>全局解释器锁</strong>（英语：<code>Global Interpreter Lock</code>，缩写<strong>GIL</strong>）</p>
<ul>
<li>
<p><code>python</code>解释器</p>
<ul>
<li><code>cpython</code>：有GIL</li>
<li><code>jpython</code>：无GIL</li>
</ul>
</li>
<li>
<p><strong>锁</strong></p>
<ul>
<li><strong>细粒度锁</strong>：是程序员主动添加的</li>
<li><strong>粗粒度锁</strong>：<strong>GIL</strong>，多核 <code>cpu</code> 只有 <code>1</code> 个线程执行，一定程度上保证了线程安全
<ul>
<li>还有特例情况（无法保证线程安全）</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>例： <code>a += 1</code></p>
<ul>
<li>在 <code>python</code> 解释器中会被翻译成 <code>bytecode</code>(字节码)，<code>a += 1</code> 可能会被翻译成多段 <code>bytecode</code>，如果解释器正在执行多段 <code>bytecode</code> 其中一段的时候被挂起去执行第二个线程，等第二个线程执行完之后再回来，接着执行 <code>a += 1</code> 剩下的几段 <code>bytecode</code> 的时候就不能保证线程安全了。</li>
<li>如果 <code>a += 1</code> 这段代码的多段 <code>bytecode</code> 会一直执行完（不会中断），则可以保证线程安全，但是<strong>GIL</strong>做不到，它只能认一段段的 <code>bytecode</code>，它是以 <code>bytecode</code> 为基本单位来执行的。</li>
</ul>
</blockquote>
<ul>
<li><code>python</code>多线程到底是不是鸡肋？
<blockquote>
<p>node.js 是单进程、单线程的语言</p>
</blockquote>
<ul>
<li><code>cpu</code>密集型程序：一段代码的大部分执行时间是消耗在 <code>cpu</code> 计算上的程序
<ul>
<li>例如：圆周率的计算、视频的解码</li>
</ul>
</li>
<li><code>IO</code>密集型程序：一段代码的大部分执行时间是消耗在<strong>查询数据库</strong>、<strong>请求网络资源</strong>、<strong>读写文件</strong>等 <code>IO</code> 操作上的程序
<ul>
<li>现实中目前写的绝大多数程序都是<code>IO</code>密集型的程序</li>
</ul>
</li>
<li><code>python</code>多线程在 <code>IO</code> 密集型程序里具有一定意义，但是不适合<code>cpu</code>密集型程序</li>
</ul>
</li>
</ul>
<h3 id="10-多线程"><a class="header-anchor" href="#10-多线程"></a>10. 多线程</h3>
<p>在多线程的情况下，多个请求传入进来，如果用同一个变量名<code>request</code>来命名多个线程里的请求会造成混乱，该如何处理？</p>
<ul>
<li>可以用字典来处理（字典是<code>python</code>中非常依赖的一种数据结构）
<ul>
<li><code>request = {key1:value1, key2:value2, key3:value3, ...}</code></li>
<li>多线程的每个线程都有它的唯一标识<code>thread_key</code></li>
<li>解决方案：<code>request = {thread_key1:Request1, thread_key2:Request2, thread_key3:Request3, ...}</code>，一个变量指向的是字典的数据结构，字典的内部包含不同的线程创建的不同的<code>Request</code>实例化对象</li>
<li>线程隔离
<ul>
<li>用不同的线程<code>id</code>号作为键，其实就是<strong>线程隔离</strong></li>
<li>不同的线程在字典中有不同的状态，各个线程的状态都被保存在字典中，互不干扰</li>
<li><strong>不同线程</strong>操作<strong>线程隔离</strong>的对象时，互不影响</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>线程<code>t1</code>操作<code>L.a</code>（对象L的属性a）与线程<code>t2</code>操作<code>L.a</code>（对象L的属性a）<br>
两者是互不干扰的，各自进行各自的操作</p>
</blockquote>
<h4 id="普通对象"><a class="header-anchor" href="#普通对象"></a>普通对象</h4>
<ul>
<li><strong>不同线程操作普通对象的情况</strong></li>
</ul>
<pre><code class="language-python">import threading
import time


class A:
    b = 1


my_obj = A()


def worker():
    # 新线程
    my_obj.b = 2


new_t = threading.Thread(target=worker, name='my_test_thread')
new_t.start()
time.sleep(1)


# 主线程
print(my_obj.b)
# 新线程的修改影响到了主线程的打印结果，因为对象A只是普通对象，不是线程隔离的对象
</code></pre>
<h4 id="线程隔离对象"><a class="header-anchor" href="#线程隔离对象"></a>线程隔离对象</h4>
<ul>
<li><strong>不同线程操作线程隔离对象的情况</strong></li>
</ul>
<pre><code class="language-python">import threading
import time

from werkzeug.local import Local


# class A(Local):
#     b = 1


my_obj = Local()
my_obj.b = 1


def worker():
    # 新线程
    my_obj.b = 2
    print('in new thread b is:' + str(my_obj.b))


new_t = threading.Thread(target=worker, name='my_test_thread')
new_t.start()
time.sleep(1)


# 主线程
print('in main thread b is:' + str(my_obj.b))
# 新线程对my_obj.b的修改不影响主线程的打印结果，因为my_obj是线程隔离的对象
</code></pre>
<ul>
<li><code>from werkzeug.local import Local</code>，<code>Local</code>是<code>werkzeug</code>包里面的，不是<code>flask</code>的</li>
<li><code>LocalStack</code>是可以用来做线程隔离的栈，封装了<code>Local</code>对象，把<code>Local</code>对象作为自己的一个属性，从而实现线程隔离的栈结构
<ul>
<li><code>Local</code>是一个可以用来做线程隔离的对象，使用字典的方式实现线程隔离</li>
<li><code>stack</code>是栈结构</li>
</ul>
</li>
</ul>
<h4 id="封装"><a class="header-anchor" href="#封装"></a><strong>封装</strong></h4>
<ul>
<li><strong>软件世界里的一切都是由封装来构建的，没有什么是封装解决不了的问题！</strong></li>
<li><strong>如果一次封装解决不了问题，那么就再来一次！</strong></li>
<li><strong>编程也是一种艺术，代码风格要含蓄！</strong></li>
</ul>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132321.jpg" alt=""></p>
<h4 id="localstack"><a class="header-anchor" href="#localstack"></a><code>LocalStack</code></h4>
<h5 id="作为栈"><a class="header-anchor" href="#作为栈"></a>作为栈</h5>
<blockquote>
<p>基本上来讲，要实现<strong>栈</strong>结构，必须要实现<code>push</code>、<code>pop</code>、<code>top</code>这三个操作<br>
<code>push</code> 推入栈<br>
<code>top</code> 取栈顶元素，不弹出该元素<br>
<code>pop</code> 取栈顶元素，并弹出该元素<br>
<strong>栈</strong>结构只能取栈顶元素（后进先出）<br>
（如果栈可以随意的按照下标去结构中的元素，那么<strong>栈</strong>和<strong>列表</strong>之类的数据结构有什么区别呢？）<br>
<strong><code>规律：很多数据结构，实际上就是限制了某些能力</code></strong></p>
</blockquote>
<pre><code class="language-python">from werkzeug.local import LocalStack


s = LocalStack()
s.push(1)

print(s.top)
print(s.top)
print(s.pop())
print(s.top)


s.push(1)
s.push(2)

print(s.top)
print(s.top)
print(s.pop())
print(s.top)
----------------------------------------------
执行结果：
1
1
1
None
2
2
2
1
</code></pre>
<h5 id="作为线程隔离对象"><a class="header-anchor" href="#作为线程隔离对象"></a>作为线程隔离对象</h5>
<blockquote>
<p>两个线程拥有两个栈，是相互隔离的，互不干扰</p>
</blockquote>
<pre><code class="language-python">import threading
import time

from werkzeug.local import LocalStack


my_stack = LocalStack() # 实例化具有线程隔离属性的LocalStack对象
my_stack.push(1)
print('in main thread after push, value is:' + str(my_stack.top))


def worker():
    # 新线程
    print('in new thread before push, value is:' + str(my_stack.top))
    # 因为线程隔离，所以在主线程中推入1跟其他线程无关，故新线程中的栈顶是没有值的（None）
    my_stack.push(2)
    print('in new thread after push, value is:' + str(my_stack.top))


new_t = threading.Thread(target=worker, name='my_new_thread')
new_t.start()
time.sleep(1)

# 主线程
print('finally, in main thread value is:' + str(my_stack.top))
# 因为线程隔离，在新线程中推入2不影响主线程栈顶值得打印
------------------------------------------------------------------------------------
执行结果：
in main thread after push, value is:1
in new thread before push, value is:None
in new thread after push, value is:2
finally, in main thread value is:1
</code></pre>
<h5 id="经典面试问题"><a class="header-anchor" href="#经典面试问题"></a><strong>经典面试问题</strong></h5>
<blockquote>
<ol>
<li><code>flask</code>使用<code>LocalStack</code>是为了隔离什么对象？<br>
答：这个问题很简单 ，什么对象被推入栈中就是为了隔离什么对象，<code>AppContext</code>（应用上下文）和<code>RequestContext</code>（请求上下文）被推入栈中，所以是为了隔离<code>AppContext</code>（应用上下文）和<code>RequestContext</code>（请求上下文）</li>
<li>为什么要隔离这些对象？<br>
表面原因：在多线程的环境下，每个线程都会创建一些对象，如果我们不把这些对象做成线程隔离的对象，那么很容易发生混淆，一旦发生混淆之后，就会造成我们程序运行的错误<br>
根本原因：我们需要用一个变量名，同时指向多个线程创建的多个实例化对象，这是不可能的。但是我们可以做到当前线程在引用到<code>request</code>这个变量名的时候，可以正确的寻找到当前线程（它自己）创建的实例化对象。</li>
<li>什么是线程隔离的对象和被线程隔离的对象？<br>
<code>LocalStack</code>和<code>Local</code>是<strong>线程隔离的对象</strong><br>
<code>AppContext</code>（应用上下文）和<code>RequestContext</code>（请求上下文）是<strong>被线程隔离的对象</strong></li>
<li><code>AppContext</code>（应用上下文）和<code>Flask</code>核心对象的区别？<br>
这两个是两个对象，<code>Flask</code>核心对象<code>app</code>将作为一个属性存在于<code>AppContext</code>（应用上下文）下！！！</li>
<li><code>Flask</code>的核心对象可以有多个吗？<br>
<code>Flask</code>核心对象在全局里只有一个。因为<code>app</code>是在入口文件里创建的，入口文件是在主线程里去执行的，所以以后无论启动多少个线程，都不会执行<code>create_app()</code>了。</li>
</ol>
</blockquote>
<ul>
<li><strong>使用线程隔离的意义</strong>：<code>使当前线程能够正确引用到他自己所创建的对象，而不是引用到其他线程所创建的对象</code></li>
</ul>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132324.jpg" alt=""><br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132326.jpg" alt=""></p>
<h3 id="11-flask开启多线程的方法"><a class="header-anchor" href="#11-flask开启多线程的方法"></a>11. <code>flask</code>开启多线程的方法</h3>
<p>在入口文件将<code>threaded=True</code>开启<br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132329.jpg" alt=""></p>
<h3 id="12-viewmodel层的作用"><a class="header-anchor" href="#12-viewmodel层的作用"></a>12. <code>ViewModel</code>层的作用</h3>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132332.jpg" alt=""></p>
<ul>
<li>页面所需数据结构与<strong>原始数据结构</strong>是一一对应的时候：
<ul>
<li>原始数据可以直接传给页面</li>
</ul>
</li>
<li>页面所需数据结构与<strong>原始数据结构</strong>不一致：
<ul>
<li>需要<code>ViewModel</code>对原始数据结构进行<code>裁剪</code>、<code>修饰</code>、<code>合并</code></li>
</ul>
</li>
</ul>
<p>因为原始数据并不一定能满足客户端显示的要求，<code>ViewModel</code>给了调整数据的机会，不同的页面对数据差异化的要求，可以在<code>ViewModel</code>里进行集中处理。</p>
<blockquote>
<p>将<code>author</code>列表转换成字符串再传给客户端：<br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132334.jpg" alt=""></p>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132337.jpg" alt=""><br>
<strong>返回<code>author</code>列表的灵活性要比返回字符串高</strong>，返回列表，客户端可以根据需求使用不同的符号将作者分割链接，但是字符串就限制了客户端的选择。<br>
对于返回客户端<code>author</code>数据的格式，<code>web</code>编程经验的个人建议：</p>
<ul>
<li>如果我们正在做的是单页面前后端分离的应用程序，建议<code>author</code>保持列表的形式直接返回到客户端去，让客户端使用<code>JavaScript</code>来操作或者解析列表。</li>
<li>如果是在做网站的话，建议<code>author</code>在<code>ViewModel</code>里处理。</li>
</ul>
<p>原因：<code>JavaScript</code>处理这些事情非常方便，但如果是模板渲染的方式来渲染<code>html</code>的话，我们先把数据处理好，直接往模板里填充会是更好的选择！<br>
<strong>数据在哪里处理，可以根据实际情况而定！</strong></p>
</blockquote>
<h3 id="13-面向对象"><a class="header-anchor" href="#13-面向对象"></a>13. 面向对象</h3>
<h4 id="面向对象的类"><a class="header-anchor" href="#面向对象的类"></a><code>面向对象</code>的类</h4>
<ul>
<li>描述自己的特征（数据）
<ul>
<li>使用类变量、实例变量来描述自己的特征</li>
</ul>
</li>
<li>行为
<ul>
<li>用方法来定义类的行为</li>
</ul>
</li>
</ul>
<blockquote>
<p>对面向对象理解不够深刻的同学经常写出只有行为没有特征的类，他们所写的类里大量的都是方法而没有类变量、实例变量，这种类的本质还是<code>面向过程</code>，因为面向过程的思维方式是人类最为熟悉的一种思维方式，所以比较容易写出面向过程的类。<br>
<code>面向过程</code>的基本单位是函数，<code>面向对象</code>的基本单位是类<br>
虽然使用了<code>class</code>关键字并且将一些方法或者函数封装到<code>class</code>内部，但是并没有改变这种面向过程的实质。<br>
<code>面向对象</code>是一种思维方式，并不在于你的代码是怎么写的，如果说你的思维方式出现错误，那你肯定是写不出面向对象的代码的。</p>
</blockquote>
<ul>
<li>如何去审视自己的类？去判断我们写出来的类到底是不是一个<code>伪面向对象</code>？
<ul>
<li>如果一个类有大量的<strong>可以被标注为</strong><code>classmethod</code>或者<code>staticmethod</code> 的静态方法，那么你的类封装的是不好的，并没有充分利用面向对象的特性。</li>
</ul>
</li>
</ul>
<h3 id="14-代码解释权的反转"><a class="header-anchor" href="#14-代码解释权的反转"></a>14. 代码解释权的反转</h3>
<ul>
<li>代码的解释权不再由函数的编写方所定义的，而是把解释的权利交给了函数的调用方<br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132341.jpg" alt=""></li>
</ul>
<pre><code class="language-python">return jsonify(books）
# 报错，因为books是一个对象，对象时无法进行序列化的

return jsonify(books.__dict__)
# 将books取字典之后同样会报错，因为books里面包含对象，所包含的对象无法进行序列化

return json.dumps(books, default=lambda o: o.__dict__)
# 最后使用json.dumps()，完美解决问题

</code></pre>
<blockquote>
<p>在<code>json.dumps()</code>的内部处理这些别人传过来的参数的时候，我们是不知道怎么去解释它的,所以我们把解释权交给函数的调用方，由函数的调用方把不能序列化的类型转化为可以序列化的类型，转移解释权的思维使用<code>函数式编程</code>是很容易编写的。在设计<code>json.dumps()</code>的时候要求函数的调用方传递进来一个函数，传递进来的函数它的具体的实现细节是由函数的调用方编写的，我们不需要关心函数内部具体的实现细节，一旦遇到了不能序列化的对象就调用<code>func(obj)</code>函数，让<code>func(obj)</code>负责把不能序列化的类型转化为可序列化的类型，我们只需要关注<code>return</code>的结果就行了</p>
</blockquote>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132343.jpg" alt=""></p>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132346.jpg" alt=""></p>
<h3 id="15-单页面和网站的区别"><a class="header-anchor" href="#15-单页面和网站的区别"></a>15. 单页面和网站的区别</h3>
<blockquote>
<p>经典面试问题：<br>
单页面和普通网站的区别？</p>
<ul>
<li><code>单页面</code>：
<ol>
<li>并不一定只有一个页面；</li>
<li>最大的特点在于数据的渲染是在客户端进行的；</li>
<li>单页面应用程的业务逻辑，也就是说数据的运算主要还是集中在客户端，用<code>JS</code>去操作的。</li>
</ol>
</li>
<li><code>多页面普通网站</code>：大多数情况下数据的渲染或者模板的填充是在服务端进行的。</li>
</ul>
</blockquote>
<h4 id="普通网站"><a class="header-anchor" href="#普通网站"></a><strong>普通网站</strong></h4>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132352.jpg" alt=""><br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132355.jpg" alt=""></p>
<h4 id="单页面"><a class="header-anchor" href="#单页面"></a><strong>单页面</strong></h4>
<ul>
<li>单页面中<code>html</code>也是静态资源</li>
</ul>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132357.jpg" alt=""><br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132359.jpg" alt=""></p>
<h3 id="16-flask静态文件访问原理"><a class="header-anchor" href="#16-flask静态文件访问原理"></a>16. <code>flask</code>静态文件访问原理</h3>
<ul>
<li>在实例化<code>flask</code>对象的时候，可以指定<code>static_folder</code>（静态文件目录）、<code>static_url_path</code>（静态文件访问路由）</li>
</ul>
<blockquote>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132402.jpg" alt=""></p>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132404.jpg" alt=""></p>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132407.jpg" alt=""></p>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132409.jpg" alt="img"><br>
<code>_static_folder</code>和<code>_static_url_path</code>默认值为<code>None</code></p>
</blockquote>
<ul>
<li><code>blueprint</code>（蓝图）的静态资源操作与<code>flask</code>核心对象操作一样<br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132416.jpg" alt=""></li>
</ul>
<h3 id="17-模板文件的位置与修改方案"><a class="header-anchor" href="#17-模板文件的位置与修改方案"></a>17. 模板文件的位置与修改方案</h3>
<p><code>templates</code>文件位置可以由<code>template_folder</code>指定模板文件路径</p>
<ul>
<li>需求：将字典填充到<code>html</code>里，再将填充之后的<code>html</code>返回到客户端去</li>
</ul>
<p><code>flask</code>为了让我们能够在模板里面很好的解析和展示数据，它引入了一个模板引擎<code>Jinja2</code></p>
<blockquote>
<p>像<code>Jinja</code>这种可以帮助我们在<code>html</code>中渲染和填充数据的语言通常被称为<code>模板语言</code></p>
</blockquote>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132417.jpg" alt=""></p>
<p>在<code>Jinja</code>里有两个流程控制语句是经常用到的：（<code>Jinja</code>流程控制语句都必须写在<code>{% %}</code>里面）</p>
<ul>
<li><code>if</code>语句（条件语句）</li>
</ul>
<pre><code class="language-jinja">    {% if data.age < 18 %}
        <ul>{{ data.name }}</ul>
    {% elif data.age == 18%}
        <ul>do some thing</ul>
    {% else %}
        <ul>{{ data.age }}</ul>

    {% endif %}
</code></pre>
<ul>
<li><code>for in</code>语句（循环控制语句）</li>
</ul>
<pre><code class="language-jinja">    {% for foo in [1,2,3,4,5] %}
        {{ foo }}
        <div>999</div>

    {% endfor %}

    {% for key, value in data.items() %}
        {# 注意for后面跟的是键值对，用逗号分开。#}
        {# data后面要加上iterms()，要确保是个可迭代对象，否则会报错 #}
        {{ key }}
        {{ value }}

    {% endfor %}
</code></pre>
<h4 id="模板继承的用法"><a class="header-anchor" href="#模板继承的用法"></a>模板继承的用法</h4>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132420.jpg" alt=""></p>
<ol>
<li>写好一级<code>html</code>页面：<code>layout.html</code> ，包含<code>block</code>模块。<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132423.jpg" alt=""></li>
<li>再需要继承的<code>html</code>页面顶端导入基础<code>html</code>：<code>{% extends ‘layout.html' %}</code>。</li>
<li>使用<code>{{ super() }}</code>关键字可以继承一级<code>html</code>页面中的<code>block</code>模块的内容，不使用的话只能替换，无法继承。<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132424.jpg" alt=""></li>
</ol>
<h4 id="模板语言的过滤器"><a class="header-anchor" href="#模板语言的过滤器"></a>模板语言的过滤器</h4>
<p>过滤器的基本用法是在关键是后面加上竖线 <code>|</code></p>
<ul>
<li><code>default</code>过滤器：是用来判断属性是否存在的，当访问一个不存在的属性时，<code>default</code>后面的赋值才会被显示，当访问一个存在的属性时，<code>default</code>后面的赋值不会被显示。</li>
</ul>
<pre><code class="language-jinja">

{{ data.school | default=('未名') }}
</code></pre>
<ul>
<li><code>|</code> 有点像<code>linux</code>里的管道符号，其表达的意思是<code>值得传递</code></li>
</ul>
<pre><code class="language-jinja">

{{ data.name == None | default=('未名') }}

</code></pre>
<p>更复杂的示例：</p>
<pre><code class="language-jinja">

{{ data.school | default(data.school) | default=('未名') }}

</code></pre>
<ul>
<li><code>length</code>过滤器（长度过滤器）：是用来判断长度的</li>
</ul>
<pre><code class="language-jinja">

{{ data | length() }}

</code></pre>
<h3 id="18-反向构建url"><a class="header-anchor" href="#18-反向构建url"></a>18. 反向构建<code>URL</code></h3>
<p>反向构建<code>url</code>使用的是<code>url_for</code>，使用方法：</p>
<pre><code class="language-jinja">{{ url_for('static', filename='test.css') }}

</code></pre>
<p><strong>凡是涉及到<code>url</code>生成的时候，建议都使用<code>url_for</code>，例如<code>CSS</code>文件的加载、<code>JS</code>文件的加载、图片的加载，包括视图函数里重定向的时候一样可以使用<code>url_for</code>来生成</strong></p>
<ul>
<li>加载<code>css</code>文件的方法
<ul>
<li>使用硬路径
<ul>
<li>缺点：当服务器域名地址、域名端口等需要改动过的时候非常麻烦</li>
</ul>
<pre><code class="language-jinja">&lt;link rel=&quot;stylesheet&quot; href=&quot;http://localhost/5000/static/test.css&quot;&gt;
</code></pre>
</li>
<li>使用相对路径
<ul>
<li>缺点：当需要修改静态资源（css、js等）路径的时候非常麻烦</li>
</ul>
<pre><code class="language-jinja">&lt;link rel=&quot;stylesheet&quot; href=&quot;../static/test.css&quot;&gt;
</code></pre>
</li>
<li>使用反向构建<code>url</code>的方法
<ul>
<li>非常方便、完美解决问题</li>
</ul>
<pre><code class="language-jinja">&lt;link rel=&quot;stylesheet&quot; href=&quot;{{ url_for('static', filename='test.css') }}&quot;&gt;
</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="19-messaging-flash消息闪现"><a class="header-anchor" href="#19-messaging-flash消息闪现"></a>19. <code>Messaging Flash</code>消息闪现</h3>
<h4 id="消息闪现的用法"><a class="header-anchor" href="#消息闪现的用法"></a>消息闪现的用法</h4>
<ul>
<li>
<p>导入<code>flash</code>函数：<code>from flask import flash</code></p>
</li>
<li>
<p>在视图函数中调用<code>flash(message, category='message')</code>函数：</p>
<pre><code class="language-python">flash('你好，这里是消息闪现!', category='errors')
flash('Hello, this is messaging flash!', category='warning')
flash('你好，这里是消息闪现!')
</code></pre>
</li>
<li>
<p>在<code>html</code>页面使用模板语言使用<code>get_flashed_messages()</code>方法获得需要闪现的消息</p>
<ul>
<li>问题1：如何获取<code>get_flashed_messages()</code>函数的调用结果呢？</li>
<li>按照<code>python</code>惯有的操作思维，先定义一个变量，再引用这个变量就可以获得这个函数的调用结果了。</li>
<li>问题2：在模板语言里如何定义一个变量？</li>
</ul>
<pre><code class="language-jinja">
{% set messages = get_flashed_messages %}
{{ messages }}

   
-------------------------------------------
['你好，这里是消息闪现!', 'Hello, this is messaging flash!', '你好，这里是消息闪现!']

</code></pre>
</li>
<li>
<p>需要在配置文件里配置<code>SECRET_KEY</code>才能正确显示消息闪现</p>
<ul>
<li><code>SECRET_KEY</code>本身就是一串字符串，但是要尽可能的保证它是独一无二的，换句话说<code>SECRET_KEY</code>就是一个秘钥</li>
<li>当<code>flask</code>需要去操作加密数据的时候，它需要读取<code>SECRET_KEY</code>，并且把秘钥运用到它的一系列算法中，最终生成加密数据</li>
<li><code>flask</code>消息闪现需要用到<code>session</code>，<code>flask</code>里面的<code>session</code>是客户端的不是服务端的，所以说加密对<code>flask</code>来说是极其重要的，所以说我们需要给应用程序配置<code>SECRET_KEY</code></li>
<li>服务端的数据是相对比较安全的，但是如果数据是储存在客户端的，那么最好把数据加密，因为客户端是不能信任的，很容易被篡改</li>
</ul>
</li>
</ul>
<h4 id="block变量作用域"><a class="header-anchor" href="#block变量作用域"></a><code>block</code>变量作用域</h4>
<p>在一个<code>block</code>里定义的变量的作用于只存在于该<code>block</code>中，不能在其他<code>block</code>中使用</p>
<h4 id="with语句变量作用域"><a class="header-anchor" href="#with语句变量作用域"></a><code>with</code>语句变量作用域</h4>
<p>模板语言里的<code>with</code>语句内定义的变量，只能在<code>with</code>语句内部使用，不能在外部使用</p>
<pre><code class="language-jinja">{% with messages = get_flashed_messages() %}
    {{ messages }}
{% endwith %}

</code></pre>
<blockquote>
<p><strong>Filtering Flash Messages<br>
Optionally you can pass a list of categories which filters the results of get_flashed_messages(). This is useful if you wish to render each category in a separate block.</strong></p>
</blockquote>
<pre><code class="language-jinja">{% with errors = get_flashed_messages(category_filter=["error"]) %}
    {% if errors %}
        <div class="alert-message block-message error">
          <a class="close" href="#">×</a>
          <ul>
            {%- for msg in errors %}
            <li>{{ msg }}</li>
            {% endfor -%}
          </ul>
        </div>
    {% endif %}
{% endwith %}
</code></pre>
<h3 id="20-搜索页面详解"><a class="header-anchor" href="#20-搜索页面详解"></a>20. 搜索页面详解</h3>
<p>建议：</p>
<ul>
<li>加载<code>CSS</code>文件的时候，一般写在<code>html</code>文件的顶部</li>
<li>加载<code>JS</code>文件的时候，一般写在<code>html</code>文件的底部</li>
</ul>
<pre><code class="language-python">class BookViewModel:

    def __init__(self, book):
        self.title = book['title']
        self.publisher = book['publisher']
        self.author = '丶'.join(book['author'])
        self.pages = book['pages'] or ''
        self.price = book['price']
        self.summary = book['summary'] or ''
        self.image = book['image']

    @property
    def intro(self):
        intros = filter(lambda x: True if x else False,
                        [self.author, self.publisher, self.price])
        return ' / '.join(intros)
        # filter 为过滤器    
        # lambda 表达式
</code></pre>
<blockquote>
<p><code>filter</code>函数（过滤器）：<br>
过滤规则是由<code>lambda</code>表达式来定义的，<br>
如果<code>lambda</code>表达式某一项数据是<code>False</code>,那么该项数据就会被过滤掉；<br>
如果返回的是<code>Ture</code>，该项数据会被保留。</p>
</blockquote>
<p>在<code>html</code>页面使用模板语言调用<code>intro</code>的数据：</p>
<pre><code class="language-jinja">{% for book in books.books %}
        <div class="row col-padding">
            <a href="{{ url_for('web.book_detail', isbn=book.isbn) }}" class="">
                <div class="col-md-2">
                    <img class="book-img-small shadow" src="{{ book.image }}">
                </div>
                <div class="col-md-7 flex-vertical description-font">
                    <span class="title">{{ book.title }}</span>
{#                    <span>{{ [book.author | d(''), book.publisher | d('', true) , '￥' + book.price | d('')] | join(' / ') }}</span>#}
                    <span>{{ book.intro }}</span>
                    <span class="summary">{{ book.summary | default('', true) }}</span>
                </div>
            </a>
        </div>
    {% endfor %}
</code></pre>
<p>页面最终的展示效果：<br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132427.jpg" alt=""></p>
<blockquote>
<p>因为搜索结果页面展示的时候，需要展示<code>作者</code>、<code>出版社</code>、<code>价格</code>并将这三项数据使用<code>/</code>连接，但是获取的数据并不是都有这三项数据，或者原始数据包含该项数据，但是该项数据为<code>空</code>，那么就会造成<code>曹雪芹//23.00元</code>这种情况，所以为了解决这个问题，引入了<code>intro</code>函数，我们在<code>intro</code>函数里判断三项原始数据是否存在且是否为空，若存在且不为空则返回数据，如果存在且为空则返回<code>False</code>，如果不存在则返回<code>False</code>，过滤完成后得到一个<code>intros</code>列表，最后使用<code>return</code>语句将<code>intros</code>列表使用<code>/</code>连接起来再返回。</p>
</blockquote>
<h4 id="property装饰器"><a class="header-anchor" href="#property装饰器"></a><code>@property</code>装饰器</h4>
<p>使用<code>@property</code>是让<code>intro</code>函数可以作为属性的方式访问，就是将类的方法转换为属性</p>
<ul>
<li>模板语言里 intro 作为<code>函数</code>的形式访问：<code>{{ book.intro() }}</code></li>
<li>模板语言里 intro 作为<code>属性</code>的形式访问：<code>{{ book.intro }}</code></li>
</ul>
<blockquote>
<p>前文示例中的<code>intro</code>是数据，应该用属性访问的方式来获取数据，而不应该用行为的方式来表达数据</p>
</blockquote>
<p>对象的两个特性：</p>
<ul>
<li>数据是用来描述它的特征的</li>
<li>方法（或者函数）是用来描述它的行为的</li>
</ul>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132429.jpg" alt=""></p>
<h3 id="21-业务模型"><a class="header-anchor" href="#21-业务模型"></a>21. 业务模型</h3>
<ul>
<li><code>book</code>模型</li>
<li><code>user</code>模型</li>
<li><code>gift</code>模型：展示用户与书籍之间关系的</li>
</ul>
<blockquote>
<p>如何在<code>gift</code>模型中表示<code>user</code>呢？<br>
在<code>gift</code>模型里面引用<code>user</code>模型，<code>sqlalchemy</code>提供了<code>relationship</code>函数用来表明引用的关系。</p>
</blockquote>
<pre><code class="language-python">from sqlalchemy import Column, Integer, Boolean, ForeignKey, String
from sqlalchemy.orm import relationship

from app.models.base import Base


class Gift(Base):
    id = Column(Integer, primary_key=True)
    user = relationship('User')
    uid = Column(Integer, ForeignKey('user.id'))
    isbn = Column(String(15), nullable=False)
    # 因为书籍的数据是从yushu.im的 API 获取的，不是从数据库获取的，所以不能从数据库关联
    # 赠送书籍的 isbn 编号是可以重复的，原因很简单
    # 例如：A 送给 B 挪威的森林；C 也可以送给 D 挪威的森林。
    
    # 从数据库关联 book 模型的写法跟关联 user 模型的写法一样
    # book = relationship('Book')
    # bid = Column(Integer, ForeignKey('book.id'))
    launched = Column(Boolean, default=False)
    # 表明书籍有没有赠送出去，默认 False 未赠送出去
</code></pre>
<h4 id="假删除"><a class="header-anchor" href="#假删除"></a>假删除</h4>
<p>业务模型最终都会在数据库生成一条一条的记录</p>
<ul>
<li>物理删除：直接从数据库里删除记录
<ul>
<li>缺点：删除之后找不回来</li>
</ul>
</li>
</ul>
<blockquote>
<p>互联网有时候需要分析用户的行为：<br>
一个用户曾经要赠送一份礼物，后来他把赠送礼物取消了，不想再赠送书籍了。<br>
如果把这条记录直接从数据库里删除之后，是没有办法对用户的历史行为进行分析的<br>
所以大多是情况都不会采用物理删除，而是用<code>假删除</code>或者<code>软删除</code></p>
</blockquote>
<p><code>假删除</code>或者<code>软删除</code>：是新增加一个属性<code>status</code>，用<code>status</code>表示这条数据是否被删除</p>
<blockquote>
<p><code>status = Column(SmallInteger, default=1)</code><br>
<code>1</code>表示保留记录，<code>0</code>表示删除记录<br>
通过更改<code>status</code>的状态在决定是否展示这条记录，实际上这条数据一直存在于数据库当中<br>
基本上所有模型都需要<code>status</code>属性，所以将<code>status</code>写在基类里，通过继承使所有模型获得<code>status</code>属性</p>
</blockquote>
<pre><code class="language-python">from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Column, Integer, SmallInteger

db = SQLAlchemy()


class Base(db.Model):
    __abstract__ =  True    # 作用：不让 sqlalchemy 创建 Base 数据表
    create_time = Column('create_time', Integer)
    status = Column(SmallInteger, default=1)
</code></pre>
<blockquote>
<p>创建<code>Base</code>之后，<code>sqlalchemy</code>会自动创建<code>Base</code>数据表，但是我们并没有在<code>Base</code>类里定义<code>primary_key</code>，所以运行时会报错。创建<code>Base</code>类只是想让模型继承它，并不想创建<code>Base</code>数据表（没有需求，没有任何意义）。在<code>Base</code>类里加入<code>__abstract__ =  True</code>可以让<code>sqlalchemy</code>不去创建数据表</p>
</blockquote>
<h3 id="22-用户注册"><a class="header-anchor" href="#22-用户注册"></a>22. 用户注册</h3>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132432.jpg" alt=""></p>
<ul>
<li><code>request.form</code>可以获取用户提交的表单信息</li>
<li><code>wtforms.validators.DataRequired</code>验证，也就是代表了该字段为必填项，表单提交时必须非空。</li>
</ul>
<pre><code class="language-python">from wtforms import Form, StringField, PasswordField
from wtforms.validators import DataRequired, Length, Email


class RegisterForm(Form):
    email = StringField(validators=[DataRequired(), Length(8, 64), Email(message='电子邮件不符合规范')])
    password = PasswordField(validators=[DataRequired(message='密码不可以为空,请输入你的密码'), Length(6, 32)])
    nickname = StringField(validators=[DataRequired(), Length(2, 10, message='昵称至少需要两个字符，最多10个字符')])
</code></pre>
<h4 id="用户密码加密"><a class="header-anchor" href="#用户密码加密"></a>用户密码加密</h4>
<p>使用<code>werkzeug.security</code>包里的<code>generate_password_hash</code>函数对用户密码加密</p>
<pre><code class="language-python">from werkzeug.security import generate_password_hash

user.password = generate_password_hash(form.password.data)
</code></pre>
<h4 id="修改数据库表单的名称"><a class="header-anchor" href="#修改数据库表单的名称"></a>修改数据库表单的名称</h4>
<p>默认情况下，在模型里定义的字段的名字就是生成的数据库表单的名字</p>
<ul>
<li>
<p>修改生成的数据库表名称的方法：</p>
<ul>
<li>使用<code>__tablename__</code>修改</li>
</ul>
</li>
<li>
<p>修改生成的数据库表字段名称的方法：</p>
<ul>
<li><code>传入字符串</code></li>
</ul>
</li>
</ul>
<pre><code class="language-python">from sqlalchemy import Column

from app.models.base import Base


class User(Base):
    __tablename__ = 'user'  # 添加 __tablename__ 指定生成的数据库表名为 user
    _password = Column('password')  # 在 Column 里传递字符串指定表字段的名字 
    id = Column(Integer, primary_key=True)
    nickname = Column(String(24), nullable=False)
    phone_number = Column(String(18), unique=True)
    confirmed = Column(Boolean, default=False)
</code></pre>
<h4 id="python动态赋值"><a class="header-anchor" href="#python动态赋值"></a><code>python</code>动态赋值</h4>
<pre><code class="language-python">@web.route('/register', methods=['GET', 'POST'])
def register():
    form = RegisterForm(request.form)   # 实例化注册表单，获取用户提交的表单
    if request.method == 'POST' and form.validate():
        user = User()           # 实例化用户
        user.set_attrs(form)    # 将真实用户与服务器用户绑定，相应属性赋值
    return render_template('auth/register.html', form={'data': {}})
</code></pre>
<p><code>set_attrs</code>使用了<code>python</code>作为动态语言的优势，在基类<code>Base</code>里复写了<code>set_attrs</code>方法，所有模型都继承了<code>set_attrs</code>方法</p>
<pre><code class="language-python">class Base(db.Model):
    __abstract__ =  True
    create_time = Column('create_time', Integer)
    status = Column(SmallInteger, default=1)

    def set_attrs(self, attrs_dict):
        for key, value in attrs_dict.items():
            if hasattr(self, key) and key != 'id':  # 主键 id 不能修改
                setattr(self, key, value)
    # set_attrs 接收一个字典类型的参数，如果字典里的某一个 key 与模型里的某一个属性相同
    # 就把字典里 key 所对应的值赋给模型的相关属性
</code></pre>
<h4 id="自定义验证器"><a class="header-anchor" href="#自定义验证器"></a>自定义验证器</h4>
<p>如何校验业务逻辑相关的规则？（使用自定义验证器）</p>
<blockquote>
<p>比如：<code>email</code>符合电子邮箱规范，但是假如数据库里已经存在了一个同名的<code>email</code>，这种情况该怎么处理？大多数同学在写代码的时候也会进行业务性质的校验，但是很多人都会把业务性质的校验写到视图函数里面去。建议：<strong>业务性质的校验也应该放到<code>form</code>里进行统一校验</strong></p>
</blockquote>
<pre><code class="language-python">from wtforms import Form, StringField, PasswordField
from wtforms.validators import DataRequired, Length, Email, ValidationError

from app.models.user import User


class RegisterForm(Form):
    email = StringField(validators=[
        DataRequired(), Length(8, 64), Email(message='电子邮件不符合规范')])

    password = PasswordField(validators=[
        DataRequired(message='密码不可以为空,请输入你的密码'), Length(6, 32)])

    nickname = StringField(validators=[
        DataRequired(), Length(2, 10, message='昵称至少需要两个字符，最多10个字符')])
    
    # 自定义验证器，验证 email
    def validate_email(self, field):
        if User.query.filter_by(email=field.data).first():
            raise ValidationError('电子邮件已被注册')
            
    # 自定义验证器，验证 nickname
    def validate_nickname(self, field):
        if User.query.filter_by(nickname=field.data).first():
            raise ValidationError('该昵称已被注册')
</code></pre>
<h4 id="cookie"><a class="header-anchor" href="#cookie"></a><code>cookie</code></h4>
<p><code>cookie</code>本来的机制：哪个网站写入的<code>cookie</code>，哪个网站才能获取这个<code>cookie</code></p>
<blockquote>
<p>也有很多技术实现跨站<code>cookie</code>共享</p>
</blockquote>
<p><code>cookie</code>的用途：</p>
<ul>
<li>用户票据的保存</li>
<li>广告的精准投放</li>
</ul>
<h4 id="用户验证"><a class="header-anchor" href="#用户验证"></a>用户验证</h4>
<p>建议：将用户密码验证的过程放在用户模型里，而不要放在视图函数里</p>
<ul>
<li>邮箱验证
<ul>
<li>直接在数据库查询邮箱</li>
</ul>
</li>
<li>密码验证
<ul>
<li>
<ol>
<li>现将用户提交的明文密码加密</li>
</ol>
</li>
<li>
<ol start="2">
<li>再与数据库储存的加密密码进行比对</li>
</ol>
</li>
</ul>
</li>
</ul>
<pre><code class="language-python">from werkzeug.security import check_password_hash

def check_password(self, raw):
    return check_password_hash(self._password, raw)
    # raw 为用户提交的明文密码
    # self._password 为数据库储存的加密的密码
    # 使用 check_password_hash 函数可以直接进行加密验证
    # 验证过程：
    # 1.先将明文密码 raw 加密；
    # 2.再与数据库里的密码进行比对；
    # 3.如果相同则返回 True，如果不相同则返回 False
</code></pre>
<h4 id="用户登录成功"><a class="header-anchor" href="#用户登录成功"></a>用户登录成功</h4>
<p>用户登陆成功之后：</p>
<ol>
<li>需要为用户生成一个票据</li>
<li>并且将票据写入<code>cookie</code>中</li>
<li>还要负责读取和管理票据</li>
</ol>
<blockquote>
<p>所以说整个登录机制是非常繁琐的，自己去实现一整套的<code>cookie</code>管理机制是非常不明智的，<br>
幸运的是<code>flask</code>提供了一个插件，可以完全使用过这个插件来管理用户的登录信息</p>
</blockquote>
<h5 id="使用flask-login插件管理用户的登录信息"><a class="header-anchor" href="#使用flask-login插件管理用户的登录信息"></a>使用<code>flask-login</code>插件管理用户的登录信息</h5>
<ol>
<li>安装插件</li>
</ol>
<pre><code>pip install flask-login
</code></pre>
<ol start="2">
<li>插件初始<br>
因为<code>flask-login</code>插件是专为<code>flask</code>定制的插件，所以需要在<code>app</code>目录下的<code>init.py</code>文件中将插件初始化</li>
</ol>
<pre><code class="language-python">from flask_login import LoginManager

login_manager = LoginManager()
# 实例化 LoginManager


def create_app():
    app = Flask(__name__)
    app.config.from_object('app.secure')
    app.config.from_object('app.setting')
    register_blueprint(app)

    db.init_app(app)
    login_manager.init_app(app)
    # 初始化 login_manager

    # 写法一：传入关键字参数
    # db.create_all(app=app)

    # 写法二：with语句 + 上下文管理器
    with app.app_context():
        db.create_all()
    return app
</code></pre>
<ol start="3">
<li>保存用户的票据信息</li>
</ol>
<pre><code class="language-python">from flask_login import login_user

@web.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm(request.form)

    if request.method == 'POST' and form.validate():
        user = User.query.filter_by(email=form.email.data).first()

        if user and user.check_password(form.password.data):
            login_user(user, remember=True)
            # remember=True 表示免密登录，flask默认免密登录的有效时长为365天
            # 如果需要更改免密登录的有效时长，则可以在 flask 的配置文件中设置 REMENBER_COOKIE_DURATION 的值
        else:
            flash('账号不存在或密码错误')

    return render_template('auth/login.html', form=form)
</code></pre>
<blockquote>
<p>这里并不直接操作<code>cookie</code>，而是通过<code>login_user(user)</code>间接的把用户票据写入到<code>cookie</code>中。<br>
票据到底是什么？我们往<code>cookie</code>里写入的又是什么？</p>
<ul>
<li>我们往<code>login_user(user)</code>里传入了一个我们自己定义的<code>user</code>模型，那么是不是说<code>login_user(user)</code>把我们用户模型里所有的数据全部写入到<code>cookie</code>中了呢？<br>
并不是，因为这个模型是我们自己定义的，我们自己定义的数据可能非常的多，全部写入不现实；而且其中的一些信息是根本不需要写入<code>cookie</code>中的。</li>
<li>那么最关键的、最应该写入<code>cookie</code>中的是什么信息？是用户的<code>id</code>号！因为<code>id</code>号才能代表用户的身份</li>
<li>那么<code>login_user(user)</code>怎么知道我们自己定义的<code>user</code>模型下面这么多个属性里，哪一个才是代表用户身份信息的<code>id</code>号呢?<br>
所以<code>flask_login</code>这个插件要求在<code>user</code>模型下定义一系列的可以获取用户相应属性的方法，这样<code>flask_login</code>可以直接调用这些方法去获取模型的属性，而不用去识别用户属性</li>
<li>可以继承<code>flask_login</code>插件里<code>UserMixin</code>基类，从而获得这些方法（获取模型属性的方法），避免重复写。此种方法<strong>对模型里属性的名称有硬性要求</strong>，比如<code>id</code>不能为<code>id</code>，因为调用的时候会继承<code>UserMixin</code>里的方法，<code>UserMixin</code>方法里是写死了的，如果属性名称不一样需要在<code>user</code>模型里覆写获取属性的相关方法。<br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132435.jpg" alt=""></li>
</ul>
</blockquote>
<h4 id="访问权限控制"><a class="header-anchor" href="#访问权限控制"></a>访问权限控制</h4>
<p>网站的视图函数大致分为两类：</p>
<ul>
<li>需要用户登录才能访问</li>
<li>不需要登录即可访问</li>
</ul>
<blockquote>
<p>如果需要限制一个视图函数需要登录才能访问，怎么办？<br>
如果仅仅是将用户信息写入到<code>cookie</code>中，我们完全不需要引入第三方插件，但是如果考虑到要对某些视图函数做权限的控制的话，第三方插件就非常有用了。<br>
对于一个用户管理插件而言，最复杂的实现的地方就在于对权限的控制</p>
</blockquote>
<p>使用第三方插件的装饰器来进行登录权限的控制：</p>
<ol>
<li>在视图函数前加上装饰器<br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132436.jpg" alt=""></li>
</ol>
<h4 id="重定向攻击"><a class="header-anchor" href="#重定向攻击"></a>重定向攻击</h4>
<ol>
<li>当用户访问一个需要登录的视图函数的时候，会自动跳转到登录页面（生成附加<code>next</code>信息的登录页面<code>url</code>）<br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132438.jpg" alt=""></li>
<li>登录页面的<code>url</code>后面会添加<code>next</code>信息，例如：<br>
<code>http://127.0.0.1:5000/login?next=%2Fmy%2Fgifts</code><br>
<code>next</code>表示的就是登陆完成后所需要跳转的页面（重定向），一般是定向为原来需要访问的页面</li>
<li>如果用人恶意篡改<code>next</code>信息，例如：<br>
<code>http://127.0.0.1:5000/login?next=http://www.baidu.com</code><br>
使用该链接登录之后就会跳转到百度页面，这种被称为<code>重定向攻击</code></li>
<li>那么如何防止这种重定向攻击呢？<br>
需要在视图函数中判断<code>next</code>的内容是否为<code>/</code>开头，因为如果是<code>/</code>开头的话表明还是在我们域名内部跳转，要跳转到其他域名的话必须以<code>http://</code>开头<br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132440.jpg" alt=""></li>
</ol>
<blockquote>
<p><code>next.startswith()</code>函数可以判断<code>next</code>信息是否以<code>/</code>开头</p>
</blockquote>
<h3 id="23-wish模型"><a class="header-anchor" href="#23-wish模型"></a>23. <code>wish</code>模型</h3>
<h4 id="1-分析得到wish模型几乎和gift一模一样-所以直接复制过来稍微改一下就行了"><a class="header-anchor" href="#1-分析得到wish模型几乎和gift一模一样-所以直接复制过来稍微改一下就行了"></a>1. 分析得到<code>wish</code>模型几乎和<code>gift</code>一模一样，所以直接复制过来稍微改一下就行了</h4>
<h4 id="2-点击赠送此书和加入到心愿清单要求用户登录-在这两个视图函数前加上-login-required"><a class="header-anchor" href="#2-点击赠送此书和加入到心愿清单要求用户登录-在这两个视图函数前加上-login-required"></a>2. 点击<code>赠送此书</code>和<code>加入到心愿清单</code>要求用户登录，在这两个视图函数前加上<code>@login_required</code></h4>
<pre><code>- 赠送此书：`save_to_gift`
- 加入到心愿清单：`save_to_wish`
</code></pre>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132442.jpg" alt=""></p>
<h4 id="3-gift还有uid属性需要赋值-uid从哪里获取呢"><a class="header-anchor" href="#3-gift还有uid属性需要赋值-uid从哪里获取呢"></a>3. <code>gift</code>还有<code>uid</code>属性需要赋值，<code>uid</code>从哪里获取呢?</h4>
<p>当一个用户登录之后，我们可以通过第三方用户登录管理插件<code>flask_login</code>的<code>current_user</code>获取当前用户的<code>id</code>，<code>current_user</code>为什么可以获取当前用户呢？因为之前我们在<code>user</code>模型里定义了<code>get_user</code>方法，该方法可以让我们通过<code>uid</code>获取用户模型，所以这里的<code>current_user</code>本质上就是一个实例化的<code>user</code>模型，所以可以用<code>current_user.id</code>获取当前用户的<code>uid</code>。</p>
<pre><code class="language-python">from app import login_manager

@login_manager.user_loader
def get_user(uid):
    return User.query.get(int(uid))
</code></pre>
<pre><code class="language-python">@web.route('/gifts/book/&lt;isbn&gt;')
@login_required
def save_to_gifts(isbn):
    gift = Gift()
    gift.isbn = isbn
    gift.uid = current_user.id
    db.session.add(gift)
    db.session.commit(gift)     # 将数据写入到数据库
</code></pre>
<h4 id="4-鱼豆"><a class="header-anchor" href="#4-鱼豆"></a>4. 鱼豆</h4>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132444.jpg" alt=""><br>
为了后期方便修改上传一本书所获得的鱼豆数量，所以将上传一本书所获得的鱼豆数量设定为变量写到配置文件里，再在需要的时候读取配置文件。</p>
<pre><code class="language-python">BEANS_UPDATE_ONE_BOOK = 0.5

current_user.beans += current_app.config['BEANS_UPDATE_ONE_BOOK']
</code></pre>
<h4 id="5-前面第3点中的save-to-gift视图函数有很多问题"><a class="header-anchor" href="#5-前面第3点中的save-to-gift视图函数有很多问题"></a>5. 前面第3点中的<code>save_to_gift</code>视图函数有很多问题</h4>
<ul>
<li>
<p><code>isbn</code>编号没有验证</p>
<ul>
<li>不清楚传入的<code>isbn</code>编号符不符合<code>isbn</code>规范</li>
<li>不清楚传入的<code>isbn</code>编号是否已存在与数据库当中
<ul>
<li>如果这本书不在数据库，则需要上传之后才能赠送</li>
</ul>
</li>
<li>不清楚传入的<code>isbn</code>编号是否已经存在于赠送清单中
<ul>
<li>如果这本书在赠送清单，则不需要加入赠送清单了</li>
</ul>
</li>
<li>不清楚传入的<code>isbn</code>编号是否已经存在于心愿清单中
<ul>
<li>如果这本在心愿清单，表示用户没有这本书，那么用户就无法赠送这本书</li>
</ul>
</li>
</ul>
</li>
<li>
<p>在<code>user</code>模型的内部添加判断书籍能否添加到赠送清单的条件：</p>
</li>
</ul>
<pre><code class="language-python">class User(UserMixin, Base):
    ...
    
    def can_save_to_list(self, isbn):
        if is_isbn_or_key(isbn) != 'isbn':
            return False
        yushu_book = YuShuBook()
        yushu_book.search_by_isbn(isbn)
        if not yushu_book.first():
            return False
        # 不允许一个用户同时赠送多本相同的图书
        # 一个用户不能同时成为一本图书的赠送者和索要者

        # 这本图书既不在赠送清单中也不再心愿清单中才能添加
        gifting = Gift.query.filter_by(isbn=isbn, uid=self.id, lunched=False).first()
        wishing = Wish.query.filter_by(isbn=isbn, uid=self.id, lunched=False).first()
        if gifting and wishing:
            return True
        else:
            return False
</code></pre>
<ul>
<li>在<code>save_to_gift</code>视图函数中调用判断条件的函数来判断是否将书籍添加到赠送清单:</li>
</ul>
<pre><code class="language-python">@web.route('/gifts/book/&lt;isbn&gt;')
@login_required
def save_to_gifts(isbn):
    if current_user.can_save_to_list(isbn):
        gift = Gift()
        gift.isbn = isbn
        gift.uid = current_user.id
        current_user.beans += current_app.config['BEANS_UPDATE_ONE_BOOK']
        db.session.add(gift)
        db.session.commit(gift)     # 将数据写入到数据库
    else:
        flash('这本书已添加至你的赠送清单或已存在与你的心愿清单,请不要重复添加')
</code></pre>
<blockquote>
<p><strong><code>can_save_to_list</code>写在<code>user</code>里正不正确？</strong><br>
<code>can_save_to_list</code>是用来做校验的，之前做校验的时候都是建议大家把校验放在<code>Form</code>里，为什么这里没有写在<code>Form</code>里呢？其实这个原因就在于编程是没有定论的，不是说只要是校验的都要全部放在<code>Form</code>里，而是要根据你的实际情况来选择，放在<code>Form</code>里有放在<code>Form</code>里的好处，放在<code>user</code>模型里有放在<code>user</code>模型里的好处。你把<code>can_save_to_list</code>看做参数的校验，放在<code>Form</code>里是没有错的，但是这个<code>can_save_to_list</code>可不可以看做是用户的行为呢？也可以看做是用户的行为，既然是用户的行为，那么放在<code>user</code>模型里也是没有错的。而且放在<code>user</code>模型里是有好处的，好处就是它的复用性会更强一些，以后如果需要相同搞的判断你的时候，放在<code>Form</code>校验里用起来是很不方便的，但是如果放在<code>user</code>模型里用起来是相当方便的。<br>
所以说呢要根据实际情况而定，编程是没有定论的，只要你能找到充分的依据，那么你就可以这么做。</p>
</blockquote>
<h4 id="6-事物"><a class="header-anchor" href="#6-事物"></a>6. 事物</h4>
<p>事物是数据库里的概念，但是放到模型的方式里，它也是存在的。<br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132447.jpg" alt=""></p>
<p>这里是操作了两张数据表:</p>
<ul>
<li><code>gift</code>表</li>
<li><code>user</code>表</li>
</ul>
<p>如果在操作完<code>gift</code>表之后突然程序中断了，<code>user</code>表中的<code>beans</code>并没有加上，这样就会造成数据库数据的异常。所以说我们必须要保证要么两个数据表同时操作，要么都不操作，这样才能保证我们数据的完整性。<br>
那么这样在数据库保证数据一致性的方法叫做<code>事物</code></p>
<blockquote>
<p>那么如何使用<code>sqlalchemy</code>来进行事物的操作？<br>
其实<code>sqlalchemy</code>就是天然支持这种事物的，其实我们这种写法已经用到事物，为什么呢？因为在<code>db.session.commit(gift)</code>前面的所有操作，都是真正的提交到数据库里去，一直到调用<code>db.session.commit(gift)</code>才提交的。<br>
道理是这个道理，但是上述代码还是有问题的，那就是没有执行数据库的<code>rollback</code>回滚操作</p>
</blockquote>
<pre><code class="language-python">try:
    gift = Gift()
    gift.isbn = isbn
    gift.uid = current_user.id
    current_user.beans += current_app.config['BEANS_UPDATE_ONE_BOOK']
    db.session.add(gift)
    db.session.commit(gift)     # 将数据写入到数据库
except Exception as e:
    db.session.rollback()
    raise e
</code></pre>
<blockquote>
<p>为什么一定要执行<code>db.session.rollback()</code>？<br>
如果说执行<code>db.session.commit(gift)</code>的时候，出现了错误，而我们有没有进行回滚操作，不仅仅这次的插入操作失败了，还有后续的所有插入操作都会失败。<br>
建议：以后只要进行<code>db.session.commit()</code>操作都要用<code>try except</code>将其包裹起来</p>
</blockquote>
<pre><code class="language-python">try:
    ...
    db.session.add(gift)
    db.session.commit(gift)
except Exception as e:
    db.session.rollback()
    raise e
</code></pre>
<h4 id="7-python-conetentmanager"><a class="header-anchor" href="#7-python-conetentmanager"></a>7. <code>python @conetentmanager</code></h4>
<p>思考问题：对于<code>db.session.commit()</code>我们可能在整个项目的很多地方都需要使用到，每次写<code>db.session.commit()</code>的时候都要重复写这样一串代码，那么有没有什么方法可以避免写这样重复的代码呢？</p>
<ul>
<li>认识<code>python @conetentmanager</code><br>
<code>@conetentmanager</code>给了我们一个机会，让我们可以把原来不是上下文管理器的类转化为上下文管理器。<br>
假如<code>MyResource</code>是<code>flask</code>提供给我们的或者是其他第三方类库提供给我们的话，我们去修改别人的源码，在源码里添加<code>__enter__</code>方法和<code>__exit__</code>方法这样合适吗？显然不合适。但是我们可以在<code>MyResource</code>的外部把<code>MyResource</code>包装成上下文管理器，</li>
</ul>
<pre><code class="language-python">class MyResource:
    # def __enter__(self):
    #     print('connect to resource')
    #     return self
    #
    # def __exit__(self, exc_type, exc_val, exc_tb):
    #     print('close resource connection')

    def query(self):
        print('query data')


# with MyResource as r:
#     r.query()


from contextlib import contextmanager

@contextmanager
def Make_Resource():
    print('connect to resource')
    yield MyResource()
    print('close resource connection')


with Make_Resource() as r:
    r.query()

----------------------------------------------------------------
运行结果：
connect to resource
query data
close resource connection
</code></pre>
<blockquote>
<p>带有<code>yield</code>关键字的函数叫做<code>生成器</code><br>
<code>yield</code>关键字可以让函数在处理到<code>yield</code>返回<code>MyResource</code>之后处于<code>中断</code>的状态，然后让程序在外面执行完<code>r.query()</code>之后再次回到<code>yield</code>这里执行后面的代码。</p>
</blockquote>
<p>我们整体来看下，这里使用<code>@conetentmanager</code>之后与正常使用<code>with</code>语句的代码到底是减少了还是增多了？<br>
很多教程里都说<code>@conetentmanager</code>这个内置的装饰器可以简化上下文管理器的定义，但是我不这么认为，我认为这种做法是完全不正确的。本身的<code>@conetentmanager</code>装饰器在理解上就是比较抽象的，其实还不如<code>__enter__</code>和<code>__exit__</code>方法来的直接。</p>
<ul>
<li>灵活运用<code>@conetentmanager</code></li>
</ul>
<p>需求场景：我现在需要打印一本书的名字《且将生活一饮而尽》，书名前后需要使用书名号《》将书名括起来。这个书名是我从数据库里查出来的，我们在数据库里保存书名的时候肯定不会在书名前后加上书名号，我现在取出来了，我想让它在显示的时候加上书名号，怎么办呢？有没有什么办法可以自动把书名号加上呢？<br>
答：可以使用<code>@conetentmanager</code>内置装饰器轻松的在书名的前后加上书名号</p>
<pre><code class="language-python">from contextlib import contextmanager

print('《且将生活一饮而尽》')

@contextmanager
def book_mark():
    print('《', end='')
    yield
    print('》', end='')

with book_mark():
    print('且将生活一饮而尽', end='')
    
-----------------------------------------------
运行结果：
《且将生活一饮而尽》
《且将生活一饮而尽》
</code></pre>
<ul>
<li>使用<code>@conetentmanager</code>重构代码<br>
我们最核心的代码是<code>db.session.commit()</code>，但是我现在想在<code>db</code>下面新增一个方法，然后我们用<code>with</code>语句去调用<code>db</code>下面我们自己定义的方法，就可以实现自动在前后加上<code>try</code>和<code>except</code>。</li>
</ul>
<blockquote>
<p><code>db</code>是<code>sqlalchemy</code>第三方插件的，我们如何在第三方类库里面新增加一个方法呢？<br>
很简单，继承<code>sqlalchemy</code></p>
</blockquote>
<blockquote>
<p><strong>小技巧</strong>：<br>
有时候我们在给类定义子类的时候，子类的名字非常难取，那么子类的名字难取，那不如我们先更改父类的名字，然后将之前父类的名字给子类（使用<code>from import</code>更改父类的名字）<br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132454.jpg" alt=""></p>
</blockquote>
<pre><code class="language-python">@web.route('/gifts/book/&lt;isbn&gt;')
@login_required
def save_to_gifts(isbn):
    if current_user.can_save_to_list(isbn):
        # try:
        with db.auto_commit():
            gift = Gift()
            gift.isbn = isbn
            gift.uid = current_user.id
            current_user.beans += current_app.config['BEANS_UPDATE_ONE_BOOK']
            db.session.add(gift)
        #     db.session.commit(gift)     # 将数据写入到数据库
        # except Exception as e:
        #     db.session.rollback()
        #     raise e
    else:
        flash('这本书已添加至你的赠送清单或已存在与你的心愿清单,请不要重复添加')
</code></pre>
<h4 id="8-为create-time赋值"><a class="header-anchor" href="#8-为create-time赋值"></a>8. 为<code>create_time</code>赋值</h4>
<p>我们发现<code>gift</code>数据库里有一个<code>create_time</code>字段，但是并没有值，为什么没有值呢？<br>
我们回想一个，数据库里有字段，那肯定是在我们定义模型的时候定义了该字段。<code>create_time</code>是在我们模型的基类<code>Base</code>里定义的。</p>
<p><code>create_time</code>表示用户当前行为的发生时间，该怎么给<code>create_time</code>赋值呢？<br>
<code>create_time</code>表示当前模型生成和保存的时间。用户发生行为，用户是模型的实例化，所以肯定是在用户模型的实例属性里给<code>create_time</code>赋值，调用实例属性的时候就是用户发生行为的时候，所以我们可以在<code>Base</code>里定义<code>__init__</code>初始化函数，来赋值。<br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132456.jpg" alt=""></p>
<blockquote>
<p>导入<code>datetime</code>： <code>from datetime import datetime</code><br>
<code>datetime.now()</code>表示获取当前时间<br>
<code>timestamp()</code>表示转化为时间戳的格式</p>
</blockquote>
<blockquote>
<p><strong>注意</strong>：<br>
类的<strong>类变量</strong>与类的<strong>实例变量</strong>的的区别：<br>
类的类变量是发生在类的定义的过程中，并不是发生在对象实例化的过程中；<br>
类的实例变量是发生在对象实例化的过程中。<br>
区别示例：<br>
如果我们在上述定义<code>create_time</code>的时候将其定义在<code>create_time = Column('create_time', Integer, default=int(datetime.now().timestamp()))</code>，那么将会导致数据库里所有的<code>create_time</code>都是同一个时间（创建基类<code>Base</code>的时间），很显然这种做法是错误的。</p>
</blockquote>
<h4 id="9-ajax技术"><a class="header-anchor" href="#9-ajax技术"></a>9.<code>ajax</code>技术</h4>
<p>对于这种：原来在什么页面，由于我们要提交某些信息，最后又要回到这个页面的这种操作，很多情情况下我们可以使用<code>ajax</code>技术。<br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132459.jpg" alt=""></p>
<blockquote>
<p><code>ajax</code>是前段的技术，做网站归最网站，但是也要善于使用<code>ajax</code>技术来改善我们服务器的性能。</p>
</blockquote>
<p>在上述过程不使用<code>ajax</code>技术的时候，最消耗服务器性能的是<code>book_detail</code>，<code>book_detail</code>又要把详情页面模板渲染再返回，这个模板渲染是最消耗服务器性能的。</p>
<p><strong>解决办法</strong>：把整个页面当做静态文件缓存起来，也是很多网站经常用到的一种技术。缓存起来之后，直接把页面从缓存读取出来再返回回去就行了。</p>
<h4 id="10-将数据库里的时间戳转换为正常的年月日"><a class="header-anchor" href="#10-将数据库里的时间戳转换为正常的年月日"></a>10. 将数据库里的时间戳转换为正常的年月日</h4>
<pre><code class="language-mermaid">graph TD
数据库时间戳 --&gt; python时间对象
python时间对象 --&gt;正常时间
</code></pre>
<p><code>time=single.create_time.strftime('%Y-%m-%d')</code><br>
<code>create_time</code>是一个整数，整数下面是没有<code>strftime()</code>方法的，只有<code>python</code>的时间类型才有<code>strftime()</code>方法，所以需要先把<code>create_time</code>转化为<code>python</code>的时间类型对象。</p>
<p>因为<code>create_time</code>是所有模型都有的属性，所以建议在模型的基类里进行转换。使用<code>fromtimestamp()</code>函数转换。</p>
<pre><code class="language-python">@property
def create_datetime(self):
    if self.create_time:
        return datetime.fromtimestamp(self.create_time)
    else:
        return None
</code></pre>
<pre><code class="language-python">def __map_to_trade(self, single):
    if single.create_datetime:
        time = single.create_datetime.strftime('%Y-%m-%d')
    else:
        time = '未知'
    return dict(
        user_name=single.user.nickname,
        time=time,
        id=single(id)
    )
</code></pre>
<h4 id="11-再次提到mvc"><a class="header-anchor" href="#11-再次提到mvc"></a>11. 再次提到<code>MVC</code></h4>
<ul>
<li>
<p><code>MVC</code>：<br>
<code>M</code>：模型层，对应<code>Models</code>，例如：<code>book</code>模型、<code>user</code>模型、<code>gift</code>模型、<code>wish</code>模型<br>
<code>V</code>：视图层，对应<code>Template</code>模板<br>
<code>C</code>：控制层，对应视图函数<br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132501.jpg" alt="img"></p>
</li>
<li>
<p><code>Django</code>里的<code>MVT</code>：<br>
<code>M</code>：模型层<br>
<code>V</code>：控制层<br>
<code>T</code>：视图层</p>
</li>
</ul>
<blockquote>
<p><strong>经典面试问题</strong>：<br>
业务逻辑应该写在<code>MVC</code>的哪一层里？<br>
答：业务逻辑应该写在<code>M</code>模型层里<br>
很多同学会回答业务逻辑应该写在<code>C</code>控制层里面，这个答案是错误的。<br>
那是因为他们没有搞清楚模型层和<code>数据层</code>的区别，在早期的时候，数据层的概念确实是存在的，它的全称应该叫做<code>数据持久化层</code>。<br>
<code>数据持久化层</code>它的主要作用是什么？<br>
以前我们没有<code>ORM</code>，当我们的模型层要使用数据的时候，它是有可能需要使用到不同的数据库的，比如有些数据是储存在<code>Oracle</code>、有些数据是储存在<code>MySQL</code>里的、还有些数据是储存在<code>MongoDB</code>里的，由于这些数据的数据源不通，有时候它们的一些具体的<code>SQL</code>的操作方法也不同，但是为了让我们模型使用更加舒服，要保持一个不同数据库的统一调用接口，我们需要用数据层来做封装。但是我们<code>ORM</code>就不需要关注底层接口的，<code>ORM</code>已经帮我们做好了封装。比如我们的<code>SQLAlchemy</code>，它自身就可以完成对接不同的数据库。<br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132502.jpg" alt=""></p>
</blockquote>
<blockquote>
<p><strong>小知识</strong>：<br>
<code>Model</code>模型层是可以分层的，在<strong>复杂的业务逻辑</strong>里我们可以在<code>Model</code>模型层里进一步的细分为<code>Model</code>、<code>Logic</code>、<code>Service</code><br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132505.jpg" alt=""></p>
</blockquote>
<h4 id="12-复写filter-by"><a class="header-anchor" href="#12-复写filter-by"></a>12. 复写<code>filter_by</code></h4>
<ul>
<li><strong>问题背景</strong>：<br>
我们之前所有的查询语句里都是有个严重的错误的，那么这个错误在什么地方呢？<br>
我们项目里采用的数据删除方式不是物理删除，而是软删除，而软删除是通过一个状态标示位<code>status</code>来表示这条数据是否已被删除，如果我们在查询的时候不加上这个关键条件<code>status=1</code>的话，那么我们查询出来的结果会包括已经被我们删掉的数据</li>
<li><strong>解决方法</strong>：<br>
我们确实可以在每个<code>filter_by</code>里面加上<code>status=1</code>的搜索条件，但是这样会很繁琐。换种思维方式，既然我要做数据库的查询，那么我的目的就是要查询没有被删除的数据。我们所有的查询条件都是传入到<code>filter_by</code>这个查询函数里的，那么我们其实是可以考虑改写<code>filter_by</code>这个函数内部的相关代码从而让我们的查询条件可以自动覆盖<code>status=1</code>。<br>
问题是这个<code>filter_by</code>函数不是我们自己定义的，它是第三方库<code>SQLAlchemy</code>提供的，最容易想到的方案就是继承相关的类，然后用自己的实现方法去覆盖这个<code>filter_by</code>函数。<br>
如果我们要去覆盖类库里面的相关对象的话，一定要搞清楚这个类库对象的继承关系。</li>
</ul>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132508.jpg" alt=""></p>
<blockquote>
<p><strong>小知识</strong>：</p>
<ul>
<li>上图中的<code>**kwargs</code>实际上就是一组查询条件：<code>isbn=isbn, launched=False, uid=current_user.id</code>类似这种，我们只需要在这组查询条件里添加上<code>status=1</code>是不是就可以了？</li>
<li>那么<code>**kwargs</code>到底是什么呢?<br>
这就比较考验同学的<code>python</code>基础，我们在<code>python</code>基础教程里面已经说过了<code>**kwargs</code>是一个字典，既然是字典那就好处理了。我们直接使用字典添加键值对的方式把<code>status=1</code>添加上就行了，<code>kwargs['status'] = 1</code>。<br>
但是我们这里最好判断一下，万一别人在使用的时候传入了<code>status=1</code>，我们就没有必要赋值了。就是使用判断字典中是否存在该键的普通方法。</li>
</ul>
</blockquote>
<pre><code class="language-python">if 'status' not in kwargs.keys():
    kwargs['status'] = 1
</code></pre>
<blockquote>
<ul>
<li>以上只是实现了自己的逻辑，我们还需要完成原有的<code>filter_by</code>的逻辑。<br>
调用基类下面<code>filter_by</code>方法就可以了<code>super(Query, self).filter_by(**kwargs)</code></li>
<li>*<em>注意：原有<code>filter_by</code>传入的参数 *<em>kwargs 是有双星号的，这里的双星号也不能少，否则会出现错误。在传入一个字典的时候必须对这个字典进行解包，使用双星号解包。</em></em><br>
最后因为原有<code>filter_by</code>函数有<code>return</code>语句，所以我们也需要添加是<code>return</code>。</li>
<li>自定义的<code>Query</code>还没有替换<code>BaseQuery</code>，那么怎么使用<code>Query</code>替换原有的<code>BaseQuery</code>呢？<br>
查看源码得知：<code>flask_sqlalchemy</code>里的<code>SQLAlchemy</code>的构造函数里是允许我们传入一个我们自己的<code>BaseQuery</code>的。<br>
所以我们只需要在实例化的时候传入我们自定义的<code>Query</code>就可以了。</li>
</ul>
</blockquote>
<pre><code class="language-python">db = SQLAlchemy(query_class=Query)
</code></pre>
<h4 id="13-复杂sql语句的编写方案"><a class="header-anchor" href="#13-复杂sql语句的编写方案"></a>13. 复杂<code>SQL</code>语句的编写方案</h4>
<p>问题背景：<br>
前面我们已经完成了搜索结果页面和图书详情页面，下面我们来完成最近上传页面。最近上传也是我们网站的首页，最近上传页面的规则如下图：<br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-11-132510.jpg" alt="img"></p>
<pre><code class="language-python">from flask import current_app
from sqlalchemy import Column, Integer, Boolean, ForeignKey, String
from sqlalchemy.orm import relationship

from app.models.base import Base


class Gift(Base):
    id = Column(Integer, primary_key=True)
    user = relationship('User')
    uid = Column(Integer, ForeignKey('user.id'))
    isbn = Column(String(15), nullable=False)
    # book = relationship('Book')
    # bid = Column(Integer, ForeignKey('book.id'))
    launched = Column(Boolean, default=False)
    # 默认值False表示书籍未赠送出去

    def recent(self):
        recent_gift = Gift.query.filter_by(launched=False).group_by(
            Gift.isbn).order_by(
            Gift.create_time).limit(
            current_app.config['RECENT_BOOK_COUNT']).distinct().all()
        return recent_gift
</code></pre>
<blockquote>
<p><strong>小技巧</strong>：</p>
<ul>
<li>第一个条件：显示30条<br>
使用<code>limit(30)</code>函数筛选</li>
<li>第二个条件：按照时间倒序排序<br>
使用<code>order_by(Gift.create_time)</code>函数倒序排序</li>
<li>第三个条件：除去书名相同的书籍<br>
在<code>MySQL</code>中有个<code>distinct()</code>函数来去重，同样在<code>SQLAlchemy</code>中同样有<code>distinct()</code>函数为我们进行去重操作。在<code>MySQL</code>中如果你想去重的话必须先<strong>分组</strong>，<strong>分组</strong>要用的是<code>group_by()</code>函数，因为我们是要除去相同书名的书籍，实际上也就是相同<code>isbn</code>的书籍，所以我们按照<code>isbn</code>来进行分组。</li>
</ul>
</blockquote>
<h4 id="14-链式调用"><a class="header-anchor" href="#14-链式调用"></a>14. 链式调用</h4>
<p>观察上述写法：<code>filter_by().group_by().order_by()</code><br>
这种写法在优秀代码编写方案里面叫做<strong>链式调用</strong>。链式调用在很多语言里是经常被使用的一种方式。</p>
<p>链式调用的<strong>特点</strong>：</p>
<ul>
<li><strong>主体</strong>
<ul>
<li>在上述代码中主体是<code>Query</code>对象</li>
</ul>
</li>
<li>通常有<strong>子函数</strong>
<ul>
<li>在上述写代码中子函数就是<code>filter_by()</code>、<code>group_by()</code>、<code>order_by()</code></li>
<li>子函数和<code>Query</code>之间最大的特点是什么？各个子函数都会返回主体<code>Query</code></li>
</ul>
</li>
<li>必须要有<strong>触发语句</strong>
<ul>
<li>上述代码中的触发语句就是<code>all()</code>，还有类似的<code>first()</code></li>
<li>一般整个链式调用遇到触发语句之后才会生成<code>SQL</code>语句去数据库里查询</li>
</ul>
</li>
</ul>
<p>链式调用的<strong>优点</strong>：</p>
<ul>
<li>提供了极大的灵活性。</li>
</ul>
<p>我们再来分析一下把<code>rencent</code>函数放到<code>Gift</code>模型下面的合理性？</p>
<p><code>Gift</code>模型按照我们的常规的思维可以把它理解成数据库里面的一条记录。那么既然<code>Gift</code>代表的是一条记录，一条记录里面有这样的<code>rencent</code>取最近的很多条记录会感觉非常的不合理，如果<code>rencent</code>是一个实例方法的话，放在<code>Gift</code>下面确实是不合适的，因为一个被实例化的<code>Gift</code>对象代表的是一个礼物，一个礼物里面有取多个礼物的方法确实是不合适的，但是如果说我们把<code>rencent</code>实例方法变成类方法的话，放在我们<code>Gift</code>类下面，那么就是合适的。</p>
<pre><code class="language-python">def recent(self):
    recent_gift = Gift.query.filter_by(launched=False).group_by(
        Gift.isbn).order_by(
        Gift.create_time).limit(
      current_app.config['RECENT_BOOK_COUNT']).distinct().all()
    return recent_gift
</code></pre>
<p>将<strong>实例方法</strong>改写成<strong>类方法</strong>：</p>
<pre><code class="language-python">@classmethod
    def recent(cls):
        recent_gift = Gift.query.filter_by(launched=False).group_by(
            Gift.isbn).order_by(
            Gift.create_time).limit(
            current_app.config['RECENT_BOOK_COUNT']).distinct().all()
        return recent_gift
</code></pre>

<div id="gitalk-container"></div>
<script src="https://cdn.bootcss.com/blueimp-md5/2.12.0/js/md5.min.js"></script><link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css"><script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>

		<script>
		var gitalkConfig = {"clientID":"9eb5bc3ac1e1ff3ddac0","clientSecret":"4b7ae28042282281295075c2bf0c97ff1791cfeb","repo":"HexoBlogComments","owner":"Annihilater","admin":["Annihilater"],"distractionFreeMode":false};
	    gitalkConfig.id = md5(location.pathname);
		var gitalk = new Gitalk(gitalkConfig);
	    gitalk.render("gitalk-container");
	    </script>
            </div>
            <hr/>

            

    <div class="reprint" id="reprint-statement">
        
            <div class="reprint__author">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-user">
                        文章作者:
                    </i>
                </span>
                <span class="reprint-info">
                    <a href="https://www.klause.cn" rel="external nofollow noreferrer">湮灭星空</a>
                </span>
            </div>
            <div class="reprint__type">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-link">
                        文章链接:
                    </i>
                </span>
                <span class="reprint-info">
                    <a href="https://www.klause.cn/2019/04/12/python-flask-gao-ji-bian-cheng-shi-pin-bi-ji/python-flask-gao-ji-bian-cheng-shi-pin-bi-ji-1/">https://www.klause.cn/2019/04/12/python-flask-gao-ji-bian-cheng-shi-pin-bi-ji/python-flask-gao-ji-bian-cheng-shi-pin-bi-ji-1/</a>
                </span>
            </div>
            <div class="reprint__notice">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-copyright">
                        版权声明:
                    </i>
                </span>
                <span class="reprint-info">
                    本博客所有文章除特別声明外，均采用
                    <a href="https://creativecommons.org/licenses/by/4.0/deed.zh" rel="external nofollow noreferrer" target="_blank">CC BY 4.0</a>
                    许可协议。转载请注明来源
                    <a href="https://www.klause.cn" target="_blank">湮灭星空</a>
                    !
                </span>
            </div>
        
    </div>

    <script async defer>
      document.addEventListener("copy", function (e) {
        let toastHTML = '<span>复制成功，请遵循本文的转载规则</span><button class="btn-flat toast-action" onclick="navToReprintStatement()" style="font-size: smaller">查看</a>';
        M.toast({html: toastHTML})
      });

      function navToReprintStatement() {
        $("html, body").animate({scrollTop: $("#reprint-statement").offset().top - 80}, 800);
      }
    </script>



            <div class="tag_share" style="display: block;">
                <div class="post-meta__tag-list" style="display: inline-block;">
                    
                        <div class="article-tag">
                            
                                <a href="/tags/python/">
                                    <span class="chip bg-color">python</span>
                                </a>
                            
                                <a href="/tags/flask/">
                                    <span class="chip bg-color">flask</span>
                                </a>
                            
                        </div>
                    
                </div>
                <div class="post_share" style="zoom: 80%; width: fit-content; display: inline-block; float: right; margin: -0.15rem 0;">
                    <link rel="stylesheet" type="text/css" href="/libs/share/css/share.min.css">

<div id="article-share">
    
    
    <div class="social-share" data-sites="twitter,facebook,google,qq,qzone,wechat,weibo,douban,linkedin" data-wechat-qrcode-helper="<p>微信扫一扫即可分享！</p>"></div>
    <script src="/libs/share/js/social-share.min.js"></script>
    

    

</div>

                </div>
            </div>
            
        </div>
    </div>

    

    

    

    

    

    

<article id="prenext-posts" class="prev-next articles">
    <div class="row article-row">
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge left-badge text-color">
                <i class="fas fa-chevron-left"></i>&nbsp;上一篇</div>
            <div class="card">
                <a href="/2019/04/12/python-flask-gao-ji-bian-cheng-shi-pin-bi-ji/python-flask-gao-ji-bian-cheng-shi-pin-bi-ji-2/">
                    <div class="card-image">
                        
                        
                        <img src="/medias/featureimages/3.jpg" class="responsive-img" alt="fisher2">
                        
                        <span class="card-title">fisher2</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            我们再来分析一下把rencent函数放到Gift模型下面的合理性？
Gift模型按照我们的常规的思维可以把它理解成数据库里面的一条记录。那么既然Gift代表的是一条记录，一条记录里面有这样的rencent取最近的很多条记录会感觉非常的不合理
                        
                    </div>
                    <div class="publish-info">
                        <span class="publish-date">
                            <i class="far fa-clock fa-fw icon-date"></i>2019-04-12
                        </span>
                        <span class="publish-author">
                            
                            <i class="fas fa-bookmark fa-fw icon-category"></i>
                            
                            <a href="/categories/fisher/" class="post-category">
                                    fisher
                                </a>
                            
                            
                        </span>
                    </div>
                </div>
                
                <div class="card-action article-tags">
                    
                    <a href="/tags/python/">
                        <span class="chip bg-color">python</span>
                    </a>
                    
                    <a href="/tags/flask/">
                        <span class="chip bg-color">flask</span>
                    </a>
                    
                </div>
                
            </div>
        </div>
        
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge right-badge text-color">
                下一篇&nbsp;<i class="fas fa-chevron-right"></i>
            </div>
            <div class="card">
                <a href="/2019/04/12/python-flask-gao-ji-bian-cheng-shi-pin-bi-ji/flask-gao-ji-bian-cheng-zong-jie/">
                    <div class="card-image">
                        
                        
                        <img src="/medias/featureimages/15.jpg" class="responsive-img" alt="flask 高级编程总结">
                        
                        <span class="card-title">flask 高级编程总结</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            Flask 高级编程
了解 flask 路由注册的基本原理
url 注册到蓝图上，蓝图注册到 flask 核心对象上
flask 蓝图的基本原理，蓝图是用来在大型项目中拆分包的
以查询参数的方式传参
使用 wtforms 进行数据验证
sq
                        
                    </div>
                    <div class="publish-info">
                            <span class="publish-date">
                                <i class="far fa-clock fa-fw icon-date"></i>2019-04-12
                            </span>
                        <span class="publish-author">
                            
                            <i class="fas fa-bookmark fa-fw icon-category"></i>
                            
                            <a href="/categories/fisher/" class="post-category">
                                    fisher
                                </a>
                            
                            
                        </span>
                    </div>
                </div>
                
                <div class="card-action article-tags">
                    
                    <a href="/tags/python/">
                        <span class="chip bg-color">python</span>
                    </a>
                    
                    <a href="/tags/flask/">
                        <span class="chip bg-color">flask</span>
                    </a>
                    
                </div>
                
            </div>
        </div>
        
    </div>
</article>

</div>



<!-- 代码块功能依赖 -->
<script type="text/javascript" src="/libs/codeBlock/codeBlockFuction.js"></script>

<!-- 代码语言 -->

<script type="text/javascript" src="/libs/codeBlock/codeLang.js"></script>

    
<!-- 代码块复制 -->

<script type="text/javascript" src="/libs/codeBlock/codeCopy.js"></script>


<!-- 代码块收缩 -->

<script type="text/javascript" src="/libs/codeBlock/codeShrink.js"></script>


<!-- 代码块折行 -->

<style type="text/css">
code[class*="language-"], pre[class*="language-"] { white-space: pre !important; }
</style>

    </div>
    <div id="toc-aside" class="expanded col l3 hide-on-med-and-down">
        <div class="toc-widget">
            <div class="toc-title"><i class="far fa-list-alt"></i>&nbsp;&nbsp;目录</div>
            <div id="toc-content"></div>
        </div>
    </div>
</div>

<!-- TOC 悬浮按钮. -->

<div id="floating-toc-btn" class="hide-on-med-and-down">
    <a class="btn-floating btn-large bg-color">
        <i class="fas fa-list-ul"></i>
    </a>
</div>


<script src="/libs/tocbot/tocbot.min.js"></script>
<script>
    $(function () {
        tocbot.init({
            tocSelector: '#toc-content',
            contentSelector: '#articleContent',
            headingsOffset: -($(window).height() * 0.4 - 45),
            // headingsOffset: -205,
            headingSelector: 'h2, h3, h4, h5, h6'
        });

        // modify the toc link href to support Chinese.
        let i = 0;
        let tocHeading = 'toc-heading-';
        $('#toc-content a').each(function () {
            $(this).attr('href', '#' + tocHeading + (++i));
        });

        // modify the heading title id to support Chinese.
        i = 0;
        $('#articleContent').children('h2, h3, h4, h5, h6').each(function () {
            $(this).attr('id', tocHeading + (++i));
        });

        // Set scroll toc fixed.
        let tocHeight = parseInt($(window).height() * 0.4 - 64);
        let $tocWidget = $('.toc-widget');
        $(window).scroll(function () {
            let scroll = $(window).scrollTop();
            /* add post toc fixed. */
            if (scroll > tocHeight) {
                $tocWidget.addClass('toc-fixed');
            } else {
                $tocWidget.removeClass('toc-fixed');
            }
        });

        
        /* 修复文章卡片 div 的宽度. */
        let fixPostCardWidth = function (srcId, targetId) {
            let srcDiv = $('#' + srcId);
            if (srcDiv.length === 0) {
                return;
            }

            let w = srcDiv.width();
            if (w >= 450) {
                w = w + 21;
            } else if (w >= 350 && w < 450) {
                w = w + 18;
            } else if (w >= 300 && w < 350) {
                w = w + 16;
            } else {
                w = w + 14;
            }
            $('#' + targetId).width(w);
        };

        // 切换TOC目录展开收缩的相关操作.
        const expandedClass = 'expanded';
        let $tocAside = $('#toc-aside');
        let $mainContent = $('#main-content');
        $('#floating-toc-btn .btn-floating').click(function () {
            if ($tocAside.hasClass(expandedClass)) {
                $tocAside.removeClass(expandedClass).hide();
                $mainContent.removeClass('l9');
            } else {
                $tocAside.addClass(expandedClass).show();
                $mainContent.addClass('l9');
            }
            fixPostCardWidth('artDetail', 'prenext-posts');
        });
        
    });
</script>

    

</main>



    <footer class="page-footer bg-color">
    <div class="container row center-align">
        <div class="col s12 m8 l8 copy-right">
            Copyright&nbsp;&copy;
            <span id="year">年份</span>
            <a href="https://www.klause.cn" target="_blank">湮灭星空</a>
            |&nbsp;Powered by&nbsp;<a href="https://hexo.io/" target="_blank">Hexo</a>
            |&nbsp;Theme&nbsp;<a href="https://github.com/blinkfox/hexo-theme-matery" target="_blank">Matery</a>
            <br>
            
            &nbsp;<i class="fas fa-chart-area"></i>&nbsp;站点总字数:&nbsp;<span
                class="white-color">199.2k</span>&nbsp;字
            
            
            
            
            
            
            <span id="busuanzi_container_site_pv">
                |&nbsp;<i class="far fa-eye"></i>&nbsp;总访问量:&nbsp;<span id="busuanzi_value_site_pv"
                    class="white-color"></span>&nbsp;次
            </span>
            
            
            <span id="busuanzi_container_site_uv">
                |&nbsp;<i class="fas fa-users"></i>&nbsp;总访问人数:&nbsp;<span id="busuanzi_value_site_uv"
                    class="white-color"></span>&nbsp;人
            </span>
            
            <br>
            
            <span id="sitetime">载入运行时间...</span>
            <script>
                function siteTime() {
                    window.setTimeout("siteTime()", 1000);
                    var seconds = 1000;
                    var minutes = seconds * 60;
                    var hours = minutes * 60;
                    var days = hours * 24;
                    var years = days * 365;
                    var today = new Date();
                    var startYear = "2019";
                    var startMonth = "6";
                    var startDate = "28";
                    var startHour = "0";
                    var startMinute = "0";
                    var startSecond = "0";
                    var todayYear = today.getFullYear();
                    var todayMonth = today.getMonth() + 1;
                    var todayDate = today.getDate();
                    var todayHour = today.getHours();
                    var todayMinute = today.getMinutes();
                    var todaySecond = today.getSeconds();
                    var t1 = Date.UTC(startYear, startMonth, startDate, startHour, startMinute, startSecond);
                    var t2 = Date.UTC(todayYear, todayMonth, todayDate, todayHour, todayMinute, todaySecond);
                    var diff = t2 - t1;
                    var diffYears = Math.floor(diff / years);
                    var diffDays = Math.floor((diff / days) - diffYears * 365);
                    var diffHours = Math.floor((diff - (diffYears * 365 + diffDays) * days) / hours);
                    var diffMinutes = Math.floor((diff - (diffYears * 365 + diffDays) * days - diffHours * hours) /
                        minutes);
                    var diffSeconds = Math.floor((diff - (diffYears * 365 + diffDays) * days - diffHours * hours -
                        diffMinutes * minutes) / seconds);
                    if (startYear == todayYear) {
                        document.getElementById("year").innerHTML = todayYear;
                        document.getElementById("sitetime").innerHTML = "本站已安全运行 " + diffDays + " 天 " + diffHours +
                            " 小时 " + diffMinutes + " 分钟 " + diffSeconds + " 秒";
                    } else {
                        document.getElementById("year").innerHTML = startYear + " - " + todayYear;
                        document.getElementById("sitetime").innerHTML = "本站已安全运行 " + diffYears + " 年 " + diffDays +
                            " 天 " + diffHours + " 小时 " + diffMinutes + " 分钟 " + diffSeconds + " 秒";
                    }
                }
                setInterval(siteTime, 1000);
            </script>
            
            <br>
            
            <span id="icp"><img src="/medias/icp.png" style="vertical-align: text-bottom;" />
                <a href="http://www.beian.miit.gov.cn" target="_blank">皖ICP备18005729号-1</a>
            </span>
            
        </div>
        <div class="col s12 m4 l4 social-link social-statis">
    <a href="https://github.com/Annihilater" class="tooltipped" target="_blank" data-tooltip="访问我的GitHub" data-position="top" data-delay="50">
        <i class="fab fa-github"></i>
    </a>



    <a href="mailto:yanmiexingkong@gmail.com" class="tooltipped" target="_blank" data-tooltip="邮件联系我" data-position="top" data-delay="50">
        <i class="fas fa-envelope-open"></i>
    </a>





    <a href="https://twitter.com/" class="tooltipped" target="_blank" data-tooltip="关注我的Twitter: https://twitter.com/" data-position="top" data-delay="50">
        <i class="fab fa-twitter"></i>
    </a>







    <a href="https://www.zhihu.com/" class="tooltipped" target="_blank" data-tooltip="关注我的知乎: https://www.zhihu.com/" data-position="top" data-delay="50">
        <i class="fab fa-zhihu1">知</i>
    </a>



    <a href="/atom.xml" class="tooltipped" target="_blank" data-tooltip="RSS 订阅" data-position="top" data-delay="50">
        <i class="fas fa-rss"></i>
    </a>

</div>
    </div>
</footer>

<div class="progress-bar"></div>

    <!-- 搜索遮罩框 -->
<div id="searchModal" class="modal">
    <div class="modal-content">
        <div class="search-header">
            <span class="title"><i class="fas fa-search"></i>&nbsp;&nbsp;搜索</span>
            <input type="search" id="searchInput" name="s" placeholder="请输入搜索的关键字"
                   class="search-input">
        </div>
        <div id="searchResult"></div>
    </div>
</div>

<script src="/js/search.js"></script>
<script type="text/javascript">
$(function () {
    searchFunc("/" + "search.xml", 'searchInput', 'searchResult');
});
</script>
    <!-- 回到顶部按钮 -->
<div id="backTop" class="top-scroll">
    <a class="btn-floating btn-large waves-effect waves-light" href="#!">
        <i class="fas fa-arrow-up"></i>
    </a>
</div>


    <script src="/libs/materialize/materialize.min.js"></script>
    <script src="/libs/masonry/masonry.pkgd.min.js"></script>
    <script src="/libs/aos/aos.js"></script>
    <script src="/libs/scrollprogress/scrollProgress.min.js"></script>
    <script src="/libs/lightGallery/js/lightgallery-all.min.js"></script>
    <script src="/js/matery.js"></script>

    <!-- Global site tag (gtag.js) - Google Analytics -->


    <!-- Baidu Analytics -->

    <!-- Baidu Push -->

<script>
    (function () {
        var bp = document.createElement('script');
        var curProtocol = window.location.protocol.split(':')[0];
        if (curProtocol === 'https') {
            bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
        } else {
            bp.src = 'http://push.zhanzhang.baidu.com/push.js';
        }
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(bp, s);
    })();
</script>

    
    <script src="/libs/others/clicklove.js" async="async"></script>
    
    
    <script async src="/libs/others/busuanzi.pure.mini.js"></script>
    

    

    

    

    

    
    <script type="text/javascript" src="/libs/background/ribbon-dynamic.js" async="async"></script>
    
    
    
    <script src="/libs/instantpage/instantpage.js" type="module"></script>
    

</body>

</html>
